home *** CD-ROM | disk | FTP | other *** search
/ Skunkware 98 / Skunkware 98.iso / src / mail / pine3.96.tar.gz / pine3.96.tar / pine3.96 / pine / mailview.c < prev    next >
C/C++ Source or Header  |  1996-07-11  |  110KB  |  3,924 lines

  1. #if !defined(lint) && !defined(DOS)
  2. static char rcsid[] = "$Id: mailview.c,v 4.243 1996/07/11 22:48:43 mikes Exp $";
  3. #endif
  4. /*----------------------------------------------------------------------
  5.  
  6.             T H E    P I N E    M A I L   S Y S T E M
  7.  
  8.    Laurence Lundblade and Mike Seibel
  9.    Networks and Distributed Computing
  10.    Computing and Communications
  11.    University of Washington
  12.    Administration Builiding, AG-44
  13.    Seattle, Washington, 98195, USA
  14.    Internet: lgl@CAC.Washington.EDU
  15.              mikes@CAC.Washington.EDU
  16.  
  17.    Please address all bugs and comments to "pine-bugs@cac.washington.edu"
  18.  
  19.  
  20.    Pine and Pico are registered trademarks of the University of Washington.
  21.    No commercial use of these trademarks may be made without prior written
  22.    permission of the University of Washington.
  23.  
  24.    Pine, Pico, and Pilot software and its included text are Copyright
  25.    1989-1996 by the University of Washington.
  26.  
  27.    The full text of our legal notices is contained in the file called
  28.    CPYRIGHT, included with this distribution.
  29.  
  30.  
  31.    Pine is in part based on The Elm Mail System:
  32.     ***********************************************************************
  33.     *  The Elm Mail System  -  Revision: 2.13                             *
  34.     *                                                                     *
  35.     *             Copyright (c) 1986, 1987 Dave Taylor              *
  36.     *             Copyright (c) 1988, 1989 USENET Community Trust   *
  37.     ***********************************************************************
  38.  
  39.  
  40.   ----------------------------------------------------------------------*/
  41.  
  42. /*======================================================================
  43.      mailview.c
  44.      Implements the mailview screen
  45.      Also includes scrolltool used to display help text
  46.   ====*/
  47.  
  48.  
  49. #include "headers.h"
  50.  
  51.  
  52. /*----------------------------------------------------------------------
  53.     Saved state for scrolling text 
  54.  ----*/
  55. typedef struct scroll_text {
  56.     void *text;          /* Original text */
  57.     char **text_lines;   /* Lines to display */
  58.     FILE  *findex;     /* file containing line offsets in another file */
  59.     char  *fname;     /* name of file containing line offsets */
  60.     long top_text_line;  /* index into text array of line on top of screen */
  61.     long num_lines;      /* Calculated number lines of text to display */
  62.     int lines_allocated; /* size of array text_lines */
  63.     int screen_length;     /* screen length in lines of text displayed by
  64.               * scroll tool  (== PGSIZE). */
  65.     int screen_width;    /* screen width of current formatting */
  66.     int screen_start_line; /* First line on screen that we scroll text on */
  67.     int screen_other_lines;/* Line ons screen not used for scroll text */
  68.     short *line_lengths;   /* Lengths of lines for display, not \0 terminatd*/
  69.     SourceType  source;     /* How to interpret "text" field */
  70.     TextType    style;     /* scrolltool screen mode; message, news, etc. */
  71. } SCROLL_S;
  72.  
  73.  
  74. /*
  75.  * Def's to help in sorting out multipart/alternative
  76.  */
  77. #define    SHOW_NONE    0
  78. #define    SHOW_PARTS    1
  79. #define    SHOW_ALL_EXT    2
  80. #define    SHOW_ALL    3
  81.  
  82.  
  83. /*
  84.  * Definitions to help scrolltool
  85.  */
  86. #define SCROLL_LINES_ABOVE(X)    HEADER_ROWS(X)
  87. #define SCROLL_LINES_BELOW(X)    FOOTER_ROWS(X)
  88. #define    SCROLL_LINES(X)        max(((X)->ttyo->screen_rows               \
  89.              - SCROLL_LINES_ABOVE(X) - SCROLL_LINES_BELOW(X)), 0)
  90. #define    get_scroll_text_lines()    (scroll_state(SS_CUR)->num_lines)
  91.  
  92.  
  93. /*
  94.  * Definitions for various scroll state manager's functions
  95.  */
  96. #define    SS_NEW    1
  97. #define    SS_CUR    2
  98. #define    SS_FREE    3
  99.  
  100.  
  101. #define    PGSIZE(X)      (ps_global->ttyo->screen_rows - (X)->screen_other_lines)
  102. #define    ISRFCEOL(S)    (*(S) == '\015' && *((S)+1) == '\012')
  103.  
  104. #define TYPICAL_BIG_MESSAGE_LINES 200 
  105.  
  106.  
  107. /*
  108.  * Internal prototypes
  109.  */
  110. int        is_an_env_hdr PROTO((char *, int));
  111. void       format_env_hdr PROTO((MAILSTREAM *, long, ENVELOPE *, gf_io_t,
  112.                  char *, char *));
  113. int       format_raw_header PROTO((MAILSTREAM *, long, gf_io_t, char *));
  114. void       format_addr_string PROTO((MAILSTREAM *, long, char *, ADDRESS *,
  115.                      char *, gf_io_t));
  116. void       format_newsgroup_string PROTO((char *, char *, char *, gf_io_t));
  117. int       format_raw_hdr_string PROTO((char *, char *, gf_io_t, char *));
  118. int       format_env_puts PROTO((char *, gf_io_t));
  119. int        delineate_this_header PROTO ((char *,char *,char *,char **,char **));
  120. void       set_scroll_text PROTO((SCROLL_S *, void *, long, SourceType,
  121.                   TextType));
  122. long       scroll_scroll_text PROTO((long, int));
  123. static int print_to_printer PROTO((void *, SourceType, char *));
  124. int       search_scroll_text PROTO((long, int, char *, Pos *));
  125. void       describe_mime PROTO((BODY *, char *, int, int));
  126. void       format_mime_size PROTO((char *, BODY *));
  127. ATTACH_S  *next_attachment PROTO(());
  128. void       zero_atmts PROTO((ATTACH_S *));
  129. void       zero_scroll_text PROTO((void));
  130. void       ScrollFile PROTO((long));
  131. long       make_file_index PROTO(());
  132. char      *show_multipart PROTO((MESSAGECACHE *, int));
  133. int       mime_show PROTO((BODY *));
  134. char      *part_desc PROTO((char *,BODY *, int));
  135. SCROLL_S  *scroll_state PROTO((int));
  136. int       search_text PROTO((int, long, int, char *, Pos *));
  137. void       format_scroll_text PROTO((void));
  138. void       redraw_scroll_text PROTO((void));
  139. void       update_scroll_titlebar PROTO((char *, TextType, long, int));
  140. #ifdef    _WINDOWS
  141. int       mswin_readscrollbuf PROTO((int));
  142. int       scroll_scroll_callback PROTO((int, long));
  143. #endif
  144.  
  145.  
  146.  
  147. /*----------------------------------------------------------------------
  148.      Format a buffer with the text of the current message for browser
  149.  
  150.     Args: ps - pine state structure
  151.   
  152.   Result: The scrolltool is called to display the message
  153.  
  154.   Loop here viewing mail until the folder changed or a command takes
  155. us to another screen. Inside the loop the message text is fetched and
  156. formatted into a buffer allocated for it.  These are passed to the
  157. scrolltool(), that displays the message and executes commands. It
  158. returns when it's time to display a different message, when we
  159. change folders, when it's time for a different screen, or when
  160. there are no more messages available.
  161.   ---*/
  162.  
  163. void
  164. mail_view_screen(ps)
  165.      struct pine *ps;
  166. {
  167.     char            last_was_full_header = 0, coerce_seen_bit;
  168.     long            last_message_viewed = -1L, raw_msgno;
  169.     int             we_cancel = 0;
  170.     MESSAGECACHE   *mc;
  171.     ENVELOPE       *env;
  172.     BODY           *body;
  173.     STORE_S        *store;
  174.     gf_io_t         pc;
  175.     SourceType        src = CharStar;
  176.  
  177.     dprint(1, (debugfile, "\n\n  -----  MAIL VIEW  -----\n"));
  178.  
  179.     /*----------------- Loop viewing messages ------------------*/
  180.     do {
  181.     /*
  182.      * Check total to make sure there's something to view.  Check it
  183.      * inside the loop to make sure everything wasn't expunged while
  184.      * we were viewing.  If so, make sure we don't just come back.
  185.      */
  186.     if(mn_get_total(ps->msgmap) <= 0L || !ps->mail_stream){
  187.         q_status_message(SM_ORDER, 0, 3, "No messages to read!");
  188.         ps->next_screen = (ps->prev_screen != mail_view_screen)
  189.                 ? ps->prev_screen : mail_index_screen;
  190.         break;
  191.     }
  192.  
  193.     we_cancel = busy_alarm(1, NULL, NULL, 0);
  194.  
  195.     if(mn_get_cur(ps->msgmap) <= 0L)
  196.       mn_set_cur(ps->msgmap, 1L);
  197.  
  198.     raw_msgno = mn_m2raw(ps->msgmap, mn_get_cur(ps->msgmap));
  199.     body      = NULL;
  200.     if(!(env = mail_fetchstructure(ps->mail_stream, raw_msgno, &body))
  201.        || !(mc = mail_elt(ps->mail_stream, raw_msgno))){
  202.         q_status_message1(SM_ORDER, 3, 3, "Error getting message %s data",
  203.                   comatose(mn_get_cur(ps->msgmap)));
  204.         dprint(1, (debugfile, "!!!! ERROR fetching %s of msg %ld\n",
  205.                env ? "elt" : "env",
  206.                mn_get_cur(ps_global->msgmap)));
  207.         ps->next_screen = (ps->prev_screen != mail_view_screen)
  208.                 ? ps->prev_screen : mail_index_screen;
  209.         break;
  210.     }
  211.  
  212.     if(!mc->seen){        /* count state change in check_point */
  213.         clear_index_cache_ent(mn_get_cur(ps->msgmap));
  214.         check_point_change();
  215.         ps_global->unseen_in_view = 1;
  216.     }
  217.     else
  218.       ps_global->unseen_in_view = 0;
  219.  
  220. #if    defined(DOS) && !defined(WIN32)
  221.     /* 
  222.      * Handle big text for DOS here.
  223.      *
  224.      * judging from 1000+ message folders around here, it looks
  225.      * like 9X% of messages fall in the < 8k range, so it
  226.      * seems like this is as good a place to draw the line as any.
  227.      *
  228.      * We ALSO need to divert all news articles we read to secondary
  229.      * storage as its possible we're using c-client's NNTP driver
  230.      * which returns BOGUS sizes UNTIL the whole thing is fetched.
  231.      * Note: this is more a deficiency in NNTP than in c-client.
  232.      */
  233.     src = (mc->rfc822_size > MAX_MSG_INCORE 
  234.            || strcmp(ps->mail_stream->dtb->name, "nntp") == 0)
  235.         ? FileStar : CharStar;
  236. #endif
  237.     store = so_get(src, NULL, EDIT_ACCESS);
  238.     gf_set_so_writec(&pc, store);
  239.  
  240.     (void) format_message(raw_msgno, env, body,
  241.                   (last_message_viewed != mn_get_cur(ps->msgmap)
  242.                    || last_was_full_header == 1) ? FM_NEW_MESS : 0,
  243.                   pc);
  244.  
  245.         last_message_viewed  = mn_get_cur(ps->msgmap);
  246.         last_was_full_header = ps->full_header;
  247.  
  248.         ps->next_screen = SCREEN_FUN_NULL;
  249.         scrolltool(so_text(store), "MESSAGE TEXT",
  250.                    MessageText, src, (ATTACH_S *)NULL);
  251.  
  252.     ps_global->unseen_in_view = 0;
  253.     so_give(&store);    /* free resources associated with store */
  254.     }
  255.     while(ps->next_screen == SCREEN_FUN_NULL);
  256.  
  257.     if(we_cancel)
  258.       cancel_busy_alarm(-1);
  259.  
  260.     ps->prev_screen = mail_view_screen;
  261. }
  262.  
  263.  
  264.  
  265. /*----------------------------------------------------------------------
  266.     Add lines to the attachments structure
  267.     
  268.   Args: body   -- body of the part being described
  269.         prefix -- The prefix for numbering the parts
  270.         num    -- The number of this specific part
  271.         should_show -- Flag indicating which of alternate parts should be shown
  272.  
  273. Result: The ps_global->attachments data structure is filled in. This
  274. is called recursively to descend through all the parts of a message. 
  275. The description strings filled in are malloced and should be freed.
  276.  
  277.   ----*/
  278. void
  279. describe_mime(body, prefix, num, should_show)
  280.     BODY *body;
  281.     char *prefix;
  282.     int   num, should_show;
  283. {
  284.     PART      *part;
  285.     char       numx[512], string[200], *description;
  286.     int        n, named, use_viewer;
  287.     ATTACH_S  *a;
  288.     PARAMETER *param;
  289.  
  290.     if(!body)
  291.       return;
  292.  
  293.     if(body->type == TYPEMULTIPART){
  294.     int alt_to_show = 0;
  295.  
  296.         if(strucmp(body->subtype, "alternative") == 0){
  297.         int effort, best_effort = SHOW_NONE;
  298.  
  299.         /*---- Figure out which alternative part to display ---*/
  300.         for(part=body->contents.part, n=1; part; part=part->next, n++)
  301.           if((effort = mime_show(&part->body)) >= best_effort
  302.          && effort != SHOW_ALL_EXT){
  303.           best_effort = effort;
  304.           alt_to_show = n;
  305.           }
  306.     }
  307.  
  308.     for(part=body->contents.part, n=1; part; part=part->next, n++){
  309.         sprintf(numx, "%s%d.", prefix, n);
  310.         describe_mime(&(part->body),
  311.               (part->body.type == TYPEMULTIPART) ? numx : prefix,
  312.               n, (n == alt_to_show || !alt_to_show));
  313.     }
  314.     }
  315.     else{
  316.     format_mime_size((a = next_attachment())->size, body);
  317.  
  318.     description = (body->description)
  319.                 ? body->description
  320.             : (body->type == TYPEMESSAGE
  321.                && body->encoding <= ENCBINARY
  322.                && body->subtype
  323.                && strucmp(body->subtype, "rfc822") == 0
  324.                && body->contents.msg.env
  325.                && body->contents.msg.env->subject)
  326.                ? body->contents.msg.env->subject
  327.                : NULL;
  328.  
  329.         sprintf(string, "%s%s%.*s%s",
  330.                 type_desc(body->type, body->subtype, body->parameter, 0),
  331.                 description ? ", \"" : "",
  332.                 sizeof(string) - 20, 
  333.                 description ? description : "",
  334.                 description ? "\"": "");
  335.  
  336.         a->description = cpystr(string);
  337.         a->body        = body;
  338.     for(named = 0, param = body->parameter; param; param = param->next)
  339.       if(strucmp(param->attribute, "name") == 0){
  340.           /* 
  341.            * before we buy the name param, make sure it's not a
  342.            * name assigned by a particularly helpful UA.  cool.
  343.            */
  344.           named = strucmp(param->value, "Message Body");
  345.           break;
  346.       }
  347.  
  348.     /*
  349.      * Make sure we have the tools available to display the
  350.      * type/subtype, *AND* that we can decode it if needed.
  351.      * Of course, if it's text, we display it anyway in the
  352.      * mail_view_screen so put off testing mailcap until we're
  353.      * explicitly asked to display that segment 'cause it could
  354.      * be expensive to test...
  355.      */
  356.     use_viewer = 0;
  357.         a->can_display = (body->type == TYPETEXT
  358.               && !strucmp(body->subtype, "plain") && !named)
  359.                ? CD_DEFERRED
  360.                  : (mime_can_display(body->type, body->subtype,
  361.                          body->parameter, &use_viewer)
  362.                 && body->encoding < ENCOTHER)
  363.                   ? CD_GOFORIT
  364.                   : CD_NOCANDO;
  365.  
  366.     a->use_external_viewer = use_viewer;
  367.         a->shown = (((body->type == TYPETEXT
  368.               && ((!(*prefix && strcmp(prefix, "1.")) && num == 1)
  369.               || !(named || use_viewer)))
  370.             || (body->type == TYPEMESSAGE
  371.             && body->encoding <= ENCBINARY))
  372.             && a->can_display != CD_NOCANDO
  373.             && should_show);
  374.     a->number = (char *)fs_get((strlen(prefix) + 16)* sizeof(char));
  375.         sprintf(a->number, "%s%d",prefix, num);
  376.         (a+1)->description = NULL;
  377.         if(body->type == TYPEMESSAGE && body->encoding <= ENCBINARY
  378.        && body->subtype && strucmp(body->subtype, "rfc822") == 0){
  379.         body = body->contents.msg.body;
  380.         sprintf(numx, "%s%d.", prefix, num);
  381.         describe_mime(body, numx, 1, should_show);
  382.         }
  383.     }
  384. }
  385.  
  386.  
  387.  
  388. /*----------------------------------------------------------------------
  389.   Return a pointer to the next attachment struct
  390.     
  391.   Args: none
  392.  
  393.   ----*/
  394. ATTACH_S *
  395. next_attachment()
  396. {
  397.     ATTACH_S *a;
  398.     int       n;
  399.  
  400.     for(a = ps_global->atmts; a->description; a++)
  401.       ;
  402.  
  403.     if((n = a - ps_global->atmts) + 1 >= ps_global->atmts_allocated){
  404.     ps_global->atmts_allocated *= 2;
  405.     fs_resize((void **)&ps_global->atmts,
  406.           ps_global->atmts_allocated * sizeof(ATTACH_S));
  407.     a = &ps_global->atmts[n];
  408.     }
  409.  
  410.     return(a);
  411. }
  412.  
  413.  
  414.  
  415. /*----------------------------------------------------------------------
  416.    Zero out the attachments structure and free up storage
  417.   ----*/
  418. void
  419. zero_atmts(atmts)
  420.      ATTACH_S *atmts;
  421. {
  422.     ATTACH_S *a;
  423.  
  424.     for(a = atmts; a->description != NULL; a++){
  425.     fs_give((void **)&(a->description));
  426.     fs_give((void **)&(a->number));
  427.     }
  428.  
  429.     atmts->description = NULL;
  430. }
  431.  
  432.  
  433. char *
  434. body_type_names(t)
  435.     int t;
  436. {
  437.     static char body_type[32];
  438.     char   *p;
  439.  
  440.     strncpy(body_type,                /* copy the given type */
  441.         (t > -1 && t < TYPEMAX && body_types[t])
  442.           ? body_types[t] : "Other", 31);
  443.  
  444.     for(p = body_type + 1; *p; p++)        /* make it presentable */
  445.       if(isupper((unsigned char)*p))
  446.     *p = tolower((unsigned char)(*p));
  447.  
  448.     return(body_type);                /* present it */
  449. }
  450.  
  451.  
  452. /*----------------------------------------------------------------------
  453.   Mapping table use to neatly display charset parameters
  454.  ----*/
  455.  
  456. static struct set_names {
  457.     char *rfcname,
  458.      *humanname;
  459. } charset_names[] = {
  460.     {"US-ASCII",        "Plain Text"},
  461.     {"ISO-8859-1",        "Latin 1"},
  462.     {"ISO-8859-2",        "Latin 2"},
  463.     {"ISO-8859-3",        "Latin 3"},
  464.     {"ISO-8859-4",        "Latin 4"},
  465.     {"ISO-8859-5",        "Latin & Cyrillic"},
  466.     {"ISO-8859-6",        "Latin & Arabic"},
  467.     {"ISO-8859-7",        "Latin & Greek"},
  468.     {"ISO-8859-8",        "Latin & Hebrew"},
  469.     {"ISO-8859-9",        "Latin 5"},
  470.     {"X-ISO-8859-10",        "Latin 6"},
  471.     {"KOI8-R",            "Latin & Russian"},
  472.     {"VISCII",            "Latin & Vietnamese"},
  473.     {"ISO-2022-JP",        "Latin & Japanese"},
  474.     {"ISO-2022-KR",        "Latin & Korean"},
  475.     {"UNICODE-1-1",        "Unicode"},
  476.     {"UNICODE-1-1-UTF-7",    "Mail-safe Unicode"},
  477.     {"ISO-2022-JP-2",        "Multilingual"},
  478.     {NULL,            NULL}
  479. };
  480.  
  481.  
  482. /*----------------------------------------------------------------------
  483.   Return a nicely formatted discription of the type of the part
  484.  ----*/
  485.  
  486. char *
  487. type_desc(type, subtype, params, full)
  488.      int type, full;
  489.      char *subtype;
  490.      PARAMETER *params;
  491. {
  492.     static char  type_d[200];
  493.     int         i;
  494.     char    *p;
  495.  
  496.     p = type_d;
  497.     sstrcpy(&p, body_type_names(type));
  498.     if(full && subtype){
  499.     *p++ = '/';
  500.     sstrcpy(&p, subtype);
  501.     }
  502.  
  503.     switch(type) {
  504.       case TYPETEXT:
  505.         while(params && strucmp(params->attribute,"charset") != 0)
  506.           params = params->next;
  507.  
  508.         if(params){
  509.         for(i = 0; charset_names[i].rfcname; i++)
  510.           if(!strucmp(params->value, charset_names[i].rfcname)){
  511.           if(!strucmp(params->value, ps_global->VAR_CHAR_SET)
  512.              || !strucmp(params->value, "us-ascii"))
  513.             i = -1;
  514.  
  515.           break;
  516.           }
  517.  
  518.         if(i >= 0){            /* charset to write */
  519.         sstrcpy(&p, " (charset: ");
  520.         sstrcpy(&p, charset_names[i].rfcname
  521.                   ? charset_names[i].rfcname : "Unknown");
  522.         if(full){
  523.             sstrcpy(&p, " \"");
  524.             sstrcpy(&p, charset_names[i].humanname
  525.                 ? charset_names[i].humanname : params->value);
  526.             *p++ = '\"';
  527.         }
  528.  
  529.         sstrcpy(&p, ")");
  530.         }
  531.         }
  532.  
  533.         break;
  534.  
  535.       case TYPEAPPLICATION:
  536.         if(full && subtype && strucmp(subtype, "octet-stream") == 0){
  537.             while(params && strucmp(params->attribute,"name"))
  538.               params = params->next;
  539.  
  540.         if(params && params->value)
  541.           sprintf(p, " (Name: \"%.100s\")", params->value);
  542.         }
  543.  
  544.         break;
  545.  
  546.       case TYPEMESSAGE:
  547.     if(full && subtype && strucmp(subtype, "external-body") == 0){
  548.             while(params && strucmp(params->attribute,"access-type"))
  549.               params = params->next;
  550.  
  551.         if(params && params->value)
  552.           sprintf(p, " (%s%.100s)", full ? "Access: " : "",
  553.               params->value);
  554.     }
  555.  
  556.     break;
  557.  
  558.       default:
  559.         break;
  560.     }
  561.  
  562.     return(type_d);
  563. }
  564.      
  565.  
  566. void
  567. format_mime_size(string, b)
  568.      char *string;
  569.      BODY *b;
  570. {
  571.     char tmp[10], *p = NULL;
  572.  
  573.     *string++ = ' ';
  574.     switch(b->encoding){
  575.       case ENCBASE64 :
  576.     if(b->type == TYPETEXT)
  577.       *(string-1) = '~';
  578.  
  579.     strcpy(p = string, byte_string((3 * b->size.bytes) / 4));
  580.     break;
  581.  
  582.       default :
  583.       case ENCQUOTEDPRINTABLE :
  584.     *(string-1) = '~';
  585.  
  586.       case ENC8BIT :
  587.       case ENC7BIT :
  588.     if(b->type == TYPETEXT)
  589.       sprintf(string, "%s lines", comatose(b->size.lines));
  590.     else
  591.       strcpy(p = string, byte_string(b->size.bytes));
  592.  
  593.     break;
  594.     }
  595.  
  596.  
  597.     if(p){
  598.     for(; *p && (isdigit((unsigned char)*p)
  599.              || ispunct((unsigned char)*p)); p++)
  600.       ;
  601.  
  602.     sprintf(tmp, " %-5.5s",p);
  603.     strcpy(p, tmp);
  604.     }
  605. }
  606.  
  607.         
  608.  
  609. /*----------------------------------------------------------------------
  610.    Determine if we can show all, some or none of the parts of a body
  611.  
  612. Args: body --- The message body to check
  613.  
  614. Returns: SHOW_ALL, SHOW_ALL_EXT, SHOW_PART or SHOW_NONE depending on
  615.      how much of the body can be shown and who can show it.
  616.  ----*/     
  617. int
  618. mime_show(body)
  619.      BODY *body;
  620. {
  621.     int   effort, best_effort, vwr;
  622.     PART *p;
  623.  
  624.     if(!body)
  625.       return(SHOW_NONE);
  626.  
  627.     switch(body->type) {
  628.       default:
  629.     return(mime_can_display(body->type,body->subtype,body->parameter,&vwr)
  630.          ? ((vwr) ? SHOW_ALL_EXT : SHOW_ALL) : SHOW_NONE);
  631.  
  632.       case TYPEMESSAGE:
  633.     return(mime_show(body->contents.msg.body) == SHOW_ALL
  634.         ? SHOW_ALL: SHOW_PARTS);
  635.  
  636.       case TYPEMULTIPART:
  637.     best_effort = SHOW_NONE;
  638.         for(p = body->contents.part; p; p = p->next)
  639.       if((effort = mime_show(&p->body)) > best_effort)
  640.         best_effort = effort;
  641.  
  642.     return(best_effort);
  643.     }
  644. }
  645.         
  646.  
  647.  
  648. /*----------------------------------------------------------------------
  649.    Format a message message for viewing
  650.  
  651.  Args: msgno -- The number of the message to view
  652.        env   -- pointer to the message's envelope
  653.        body  -- pointer to the message's body
  654.        flgs  -- possible flags:
  655.                 FM_NEW_MESS -- flag indicating a different message being
  656.                    formatted than was formatted last time 
  657.                    function was called
  658.         FM_DO_PRINT -- Indicates formatted text is bound for
  659.                    something other than display by pine
  660.                    (printing, export, etc)
  661.  
  662. Result: Returns true if no problems encountered, else false.
  663.  
  664. First the envelope is formatted; next a list of all attachments is
  665. formatted if there is more than one. Then all the body parts are
  666. formatted, fetching them as needed. This includes headers of included
  667. message. Richtext is also formatted. An entry is made in the text for
  668. parts that are not displayed or can't be displayed.  source indicates 
  669. how and where the caller would like to have the text formatted.
  670.  
  671.  ----*/    
  672. int
  673. format_message(msgno, env, body, flgs, pc)
  674.     long         msgno;
  675.     ENVELOPE    *env;
  676.     BODY        *body;
  677.     int          flgs;
  678.     gf_io_t      pc;
  679. {
  680.     char     *decode_error;
  681.     HEADER_S  h;
  682.     ATTACH_S *a;
  683.     int       show_parts, error_found = 0, i;
  684.     gf_io_t   gc;
  685.  
  686.  
  687.     show_parts = !(flgs&FM_DO_PRINT);
  688.  
  689.     /*---- format and copy envelope ----*/
  690.     if(ps_global->full_header)
  691.       q_status_message(SM_INFO, 0, 3,
  692.                "Full header mode ON.  All header text being included");
  693.  
  694.     HD_INIT(&h, ps_global->VAR_VIEW_HEADERS, ps_global->view_all_except,
  695.         FE_DEFAULT);
  696.     switch(format_header_text(ps_global->mail_stream, msgno, env, &h,pc,NULL)){
  697.                   
  698.       case -1 :            /* write error */
  699.     goto write_error;
  700.  
  701.       case 1 :            /* fetch error */
  702.     if(!(gf_puts("[ Error fetching header ]",  pc)
  703.          && !gf_puts(NEWLINE, pc)))
  704.       goto write_error;
  705.  
  706.     break;
  707.     }
  708.  
  709.     if(body == NULL) {
  710.         /*--- Server is not an IMAP2bis, It can't parse MIME
  711.               so we just show the text here. Hopefully the 
  712.               message isn't a MIME message 
  713.           ---*/
  714.     void *text2;
  715. #if    defined(DOS) && !defined(WIN32)
  716.     char *append_file_name;
  717.  
  718.     /* for now, always fetch to disk.  This could be tuned to
  719.      * check for message size, then decide to deal with it on disk...
  720.      */
  721.     mail_parameters(ps_global->mail_stream, SET_GETS, (void *)dos_gets);
  722.     if(!(append_file_name = temp_nam(NULL, "pv"))
  723.        || !(append_file = fopen(append_file_name, "w+b"))){
  724.         if(append_file_name)
  725.           fs_give((void **)&append_file_name);
  726.  
  727.         q_status_message1(SM_ORDER,3,3,"Can't make temp file: %s",
  728.                   error_description(errno));
  729.         return(0);
  730.     }
  731. #endif
  732.  
  733.         if(text2 = (void *)mail_fetchtext(ps_global->mail_stream, msgno)){
  734.          if(!gf_puts(NEWLINE, pc))        /* write delimiter */
  735.           goto write_error;
  736. #if    defined(DOS) && !defined(WIN32)
  737.         gf_set_readc(&gc, append_file, 0L, FileStar);
  738. #else
  739.         gf_set_readc(&gc, text2, (unsigned long)strlen(text2), CharStar);
  740. #endif
  741.         gf_filter_init();
  742.         gf_link_filter(gf_nvtnl_local);
  743.         if(decode_error = gf_pipe(gc, pc)){
  744.                 sprintf(tmp_20k_buf, "%s%s    [Formatting error: %s]%s",
  745.             NEWLINE, NEWLINE, decode_error, NEWLINE);
  746.         if(!gf_puts(tmp_20k_buf, pc))
  747.           goto write_error;
  748.         }
  749.     }
  750.  
  751. #if    defined(DOS) && !defined(WIN32)
  752.     fclose(append_file);            /* clean up tmp file */
  753.     append_file = NULL;
  754.     unlink(append_file_name);
  755.     fs_give((void **)&append_file_name);
  756.     mail_gc(ps_global->mail_stream, GC_TEXTS);
  757.     mail_parameters(ps_global->mail_stream, SET_GETS, (void *)NULL);
  758. #endif
  759.  
  760.     if(!text2){
  761.         if(!gf_puts(NEWLINE, pc)
  762.            || !gf_puts("    [ERROR fetching text of message]", pc)
  763.            || !gf_puts(NEWLINE, pc)
  764.            || !gf_puts(NEWLINE, pc))
  765.           goto write_error;
  766.     }
  767.  
  768.     return(1);
  769.     }
  770.  
  771.     if(flgs&FM_NEW_MESS) {
  772.         zero_atmts(ps_global->atmts);
  773.         describe_mime(body, "", 1, 1);
  774.     }
  775.  
  776.     /*---- Calculate the approximate length of what we've got to show  ---*/
  777.  
  778.     /*=========== Format the header into the buffer =========*/
  779.     /*----- First do the list of parts/attachments if needed ----*/
  780.     if(show_parts && ps_global->atmts[1].description != NULL) {
  781.     int max_num_l = 0, max_size_l = 0;
  782.  
  783.     if(!gf_puts("Parts/attachments:", pc) || !gf_puts(NEWLINE, pc))
  784.       goto write_error;
  785.  
  786.         /*----- Figure display lengths for nice display -----*/
  787.         for(a = ps_global->atmts; a->description != NULL; a++) {
  788.         if(strlen(a->number) > max_num_l)
  789.           max_num_l = strlen(a->number);
  790.         if(strlen(a->size) > max_size_l)
  791.           max_size_l = strlen(a->size);
  792.     }
  793.  
  794.         /*----- Format the list of attachments -----*/
  795.         for(a = ps_global->atmts; a->description != NULL; a++) {
  796.         int i = ps_global->ttyo->screen_cols - max_num_l - max_size_l
  797.              - 14 - strlen(NEWLINE);
  798.             sprintf(tmp_20k_buf, "   %-*.*s %s  %*.*s  %-*.*s%s",
  799.                     max_num_l, max_num_l, a->number,
  800.                     (a->shown ? "Shown" : (a->can_display != CD_NOCANDO)
  801.                         ? "  OK " : "     "),
  802.                     max_size_l, max_size_l, a->size, i, i, a->description,
  803.             NEWLINE);
  804.         if(!format_env_puts(tmp_20k_buf, pc))
  805.           goto write_error;
  806.         }
  807.  
  808.     if(!gf_puts("----------------------------------------", pc)
  809.        || !gf_puts(NEWLINE, pc))
  810.       goto write_error;
  811.     }
  812.  
  813.     if(!gf_puts(NEWLINE, pc))        /* write delimiter */
  814.       goto write_error;
  815.  
  816.     show_parts = 0;
  817.  
  818.     /*======== Now loop through formatting all the parts =======*/
  819.     for(a = ps_global->atmts; a->description != NULL; a++) {
  820.  
  821.         if(!a->shown) {
  822.         if(!gf_puts(part_desc(a->number, a->body,
  823.                   (flgs&FM_DO_PRINT)
  824.                     ? 3
  825.                     : (a->can_display != CD_NOCANDO) ? 1 : 2),
  826.             pc)
  827.            || ! gf_puts(NEWLINE, pc))
  828.           goto write_error;
  829.  
  830.             continue;
  831.         } 
  832.  
  833.         switch(a->body->type){
  834.  
  835.           case TYPETEXT:
  836.         /*
  837.          * Don't write our delimiter if this text part is
  838.          * the first part of a message/rfc822 segment...
  839.          */
  840.         if(show_parts && a != ps_global->atmts 
  841.            && a[-1].body && a[-1].body->type != TYPEMESSAGE){
  842.         sprintf(tmp_20k_buf, "%s  [ Part %s: \"%.55s\" ]%s%s",
  843.             NEWLINE, a->number, 
  844.             a->body->description ? a->body->description
  845.                          : "Attached Text",
  846.             NEWLINE, NEWLINE);
  847.         if(!gf_puts(tmp_20k_buf, pc))
  848.           goto write_error;
  849.         }
  850.  
  851.         error_found += decode_text(a, msgno, pc,
  852.                        (flgs&FM_DO_PRINT) ? QStatus : InLine,
  853.                        !(flgs&FM_DO_PRINT));
  854.             break;
  855.  
  856.           case TYPEMESSAGE:
  857.             sprintf(tmp_20k_buf, "%s  [ Part %s: \"%.55s\" ]%s%s",
  858.             NEWLINE, a->number, 
  859.                     a->body->description ? a->body->description
  860.                      : "Included Message",
  861.             NEWLINE, NEWLINE);
  862.           if(!gf_puts(tmp_20k_buf, pc))
  863.           goto write_error;
  864.  
  865.         if(a->body->subtype && strucmp(a->body->subtype, "rfc822") == 0){
  866.         format_envelope(NULL, 0L, a->body->contents.msg.env, pc,
  867.                 FE_DEFAULT, NULL);
  868.         }
  869.             else if(a->body->subtype 
  870.             && strucmp(a->body->subtype, "external-body") == 0) {
  871.         if(!gf_puts("This part is not included and can be ", pc)
  872.            || !gf_puts("fetched as follows:", pc)
  873.            || !gf_puts(NEWLINE, pc)
  874.            || !gf_puts(display_parameters(a->body->parameter), pc))
  875.           goto write_error;
  876.             }
  877.         else
  878.           error_found += decode_text(a, msgno, pc, 
  879.                      (flgs&FM_DO_PRINT) ? QStatus : InLine,
  880.                      !(flgs&FM_DO_PRINT));
  881.  
  882.         if(!gf_puts(NEWLINE, pc))
  883.           goto write_error;
  884.  
  885.             break;
  886.  
  887.           default:
  888.         if(!gf_puts(part_desc(a->number,a->body,(flgs&FM_DO_PRINT) ? 3:1),
  889.             pc))
  890.           goto write_error;
  891.         }
  892.  
  893.     show_parts++;
  894.     }
  895.  
  896.     return(!error_found);
  897.  
  898.   write_error:
  899.  
  900.     if(flgs & FM_DO_PRINT)
  901.       q_status_message1(SM_ORDER, 3, 4, "Error writing message: %s", 
  902.             error_description(errno));
  903.     return(0);
  904. }
  905.  
  906.  
  907. /*
  908.  *  This is a list of header fields that are represented canonically
  909.  *  by the c-client's ENVELOPE structure.  The list is used by the
  910.  *  two functions below to decide if a given field is included in this
  911.  *  set.
  912.  */
  913. static struct envelope_s {
  914.     char *name;
  915.     long val;
  916. } envelope_hdrs[] = {
  917.     {"from",        FE_FROM},
  918.     {"sender",        FE_SENDER},
  919.     {"date",        FE_DATE},
  920.     {"to",        FE_TO},
  921.     {"cc",        FE_CC},
  922.     {"bcc",        FE_BCC},
  923.     {"newsgroups",    FE_NEWSGROUPS},
  924.     {"subject",        FE_SUBJECT},
  925.     {"message-id",    FE_MESSAGEID},
  926.     {"reply-to",    FE_REPLYTO},
  927.     {"in-reply-to",    FE_INREPLYTO},
  928.     {NULL,        0}
  929. };
  930.  
  931.  
  932.  
  933. /*
  934.  * is_an_env_hdr - is this name a header in the envelope structure?
  935.  *
  936.  *             name - the header name to check
  937.  * news_is_env_hack - Hack to compensate for c-client deficiency.  Newsgroups
  938.  *                    are normally part of the envelope, unless it is over
  939.  *                    imap, in which case c-client fails to fill it in for us.
  940.  */
  941. int
  942. is_an_env_hdr(name, news_is_env_hack)
  943. char *name;
  944. int   news_is_env_hack;
  945. {
  946.     register int i;
  947.  
  948.     if(!news_is_env_hack && !strucmp(name, "newsgroups"))
  949.       return(0);
  950.  
  951.     for(i = 0; envelope_hdrs[i].name; i++)
  952.       if(!strucmp(name, envelope_hdrs[i].name))
  953.     return(1);
  954.  
  955.     return(0);
  956. }
  957.  
  958.  
  959.  
  960. /*
  961.  * Format a single field from the envelope
  962.  */
  963. void
  964. format_env_hdr(stream, msgno, env, pc, field, prefix)
  965.     MAILSTREAM *stream;
  966.     long    msgno;
  967.     ENVELOPE   *env;
  968.     gf_io_t    pc;
  969.     char       *field;
  970.     char       *prefix;
  971. {
  972.     register int i;
  973.  
  974.     for(i = 0; envelope_hdrs[i].name; i++)
  975.       if(!strucmp(field, envelope_hdrs[i].name)){
  976.       format_envelope(stream, msgno, env, pc, envelope_hdrs[i].val,prefix);
  977.       return;
  978.       }
  979. }
  980.  
  981.  
  982. /*
  983.  * Look through header string headers, beginning with begin, for the next
  984.  * occurrence of header field.  Set start to that.  Set end to point one
  985.  * position past all of the continuation lines that go with field.
  986.  * That is, if end is converted to a null
  987.  * character then the string start will be the next occurence of header
  988.  * field including all of its continuation lines.  Headers is assumed to
  989.  * have CRLF's as end of lines.
  990.  *
  991.  * If field is NULL, then we just leave start pointing to begin and
  992.  * make end the end of that header.
  993.  *
  994.  * Returns 1 if found, 0 if not.
  995.  */
  996. int
  997. delineate_this_header(headers, field, begin, start, end)
  998.     char  *headers, *field, *begin;
  999.     char **start, **end;
  1000. {
  1001.     char tmpfield[MAILTMPLEN+2]; /* copy of field with colon appended */
  1002.     char *p;
  1003.  
  1004.     if(field == NULL){
  1005.     if(!begin || !*begin || isspace((unsigned char)*begin))
  1006.       return 0;
  1007.     else
  1008.       *start = begin;
  1009.     }
  1010.     else{
  1011.     strncpy(tmpfield, field, MAILTMPLEN);
  1012.     tmpfield[MAILTMPLEN] = '\0';
  1013.     strcat(tmpfield, ":");
  1014.  
  1015.     *start = srchstr(begin, tmpfield);
  1016.     if(!*start)
  1017.       return 0;
  1018.     }
  1019.  
  1020.     for(p = *start; *p; p++){
  1021.     if(ISRFCEOL(p)
  1022.        && (!isspace((unsigned char)*(p+2)) || *(p+2) == '\015')){
  1023.         /*
  1024.          * The final 015 in the test above is to test for the end
  1025.          * of the headers.
  1026.          */
  1027.         *end = p+2;
  1028.         break;
  1029.     }
  1030.     }
  1031.  
  1032.     if(!*p)
  1033.       *end = p;
  1034.     
  1035.     return 1;
  1036. }
  1037.  
  1038.  
  1039.  
  1040. /*----------------------------------------------------------------------
  1041.    Handle fetching and filtering a text message segment to be displayed
  1042.    by scrolltool or printed.
  1043.  
  1044. Args: att   -- segment to fetch
  1045.       msgno -- message number segment is a part of
  1046.       pc    -- function to write characters from segment with
  1047.       style -- Indicates special handling for error messages
  1048.       display_bound -- Indicates special necessary filtering
  1049.  
  1050. Returns: 1 if errors encountered, 0 if everything went A-OK
  1051.  
  1052.  ----*/     
  1053. int
  1054. decode_text(att, msgno, pc, style, display_bound)
  1055.     ATTACH_S       *att;
  1056.     long            msgno;
  1057.     gf_io_t         pc;
  1058.     DetachErrStyle  style;
  1059.     int            display_bound;
  1060. {
  1061.     PARAMETER         *param;
  1062.     filter_t           aux_filter[5];
  1063.     int               filtcnt = 0, error_found = 0;
  1064.     char          *err;
  1065.  
  1066.     if(F_OFF(F_PASS_CONTROL_CHARS, ps_global)){
  1067.     aux_filter[filtcnt++] = gf_escape_filter;
  1068.     aux_filter[filtcnt++] = gf_control_filter;
  1069.     }
  1070.  
  1071.     if(att->body->subtype){
  1072.     filter_t rt_filt = NULL;
  1073.  
  1074.     if(!strucmp(att->body->subtype, "richtext")){
  1075.         gf_rich2plain_opt(!display_bound);    /* maybe strip everything! */
  1076.         rt_filt = gf_rich2plain;
  1077.     }
  1078.     else if(!strucmp(att->body->subtype, "enriched")){
  1079.         gf_enriched2plain_opt(!display_bound);
  1080.         rt_filt = gf_enriched2plain;
  1081.     }
  1082.  
  1083.     if(rt_filt){
  1084.         aux_filter[filtcnt++] = rt_filt;
  1085.         if(!display_bound){
  1086.         gf_wrap_filter_opt(75);  /* width to use for file or printer */
  1087.         aux_filter[filtcnt++] = gf_wrap;
  1088.         }
  1089.     }
  1090.     else if(!strucmp(att->body->subtype, "html")
  1091.         && !ps_global->full_header){
  1092.         
  1093. /*BUG:        sniff the params for "version=2.0" ala draft-ietf-html-spec-01 */
  1094. /*        aux_filter[filtcnt++] = gf_html2plain;*/
  1095.     }
  1096.     }
  1097.  
  1098.     for(param = att->body->parameter; 
  1099.         param != NULL && strucmp(param->attribute,"charset") != 0;
  1100.         param = param->next)
  1101.     ;
  1102.  
  1103.     /*
  1104.      * If there's a charset specified and it's not US-ASCII, and our
  1105.      * local charset's undefined or it's not the same as the specified
  1106.      * charset, put up a warning...
  1107.      */
  1108.     if(param && param->value && strucmp(param->value, "us-ascii")
  1109.        && (!ps_global->VAR_CHAR_SET
  1110.        || strucmp(param->value, ps_global->VAR_CHAR_SET)))
  1111.       if(!gf_puts("    [The following text is in the \"", pc)
  1112.      || !gf_puts(param->value, pc)
  1113.      || !gf_puts("\" character set]", pc)
  1114.      || !gf_puts(NEWLINE , pc)
  1115.      || !gf_puts("    [Your display is set for the \"" , pc)
  1116.      || !gf_puts(ps_global->VAR_CHAR_SET
  1117.                ? ps_global->VAR_CHAR_SET : "US-ASCII" , pc)
  1118.      || !gf_puts("\" character set]" , pc)
  1119.      || !gf_puts(NEWLINE , pc)
  1120.      || !gf_puts("    [Some characters may be displayed incorrectly]",pc)
  1121.      || !gf_puts(NEWLINE, pc)
  1122.      || !gf_puts(NEWLINE, pc))
  1123.     goto write_error;
  1124.  
  1125.     aux_filter[filtcnt] = NULL;
  1126.     err = detach(ps_global->mail_stream, msgno, att->body, att->number,
  1127.          NULL, pc, aux_filter);
  1128.     if(err) {
  1129.     error_found++;
  1130.     if(style == QStatus) {
  1131.         q_status_message1(SM_ORDER, 3, 4, "%s", err);
  1132.     } else if(style == InLine) {
  1133.         sprintf(tmp_20k_buf, "%s   [Error: %s]  %c%s%c%s%s",
  1134.             NEWLINE, err,
  1135.             att->body->description ? '\"' : ' ',
  1136.             att->body->description ? att->body->description : "",
  1137.             att->body->description ? '\"' : ' ',
  1138.             NEWLINE, NEWLINE);
  1139.         if(!gf_puts(tmp_20k_buf, pc))
  1140.           goto write_error;
  1141.     }
  1142.     }
  1143.  
  1144.     if(att->body->subtype
  1145.        && (!strucmp(att->body->subtype, "richtext")
  1146.        || !strucmp(att->body->subtype, "enriched"))
  1147.        && !display_bound){
  1148.     if(!gf_puts(NEWLINE, pc) || !gf_puts(NEWLINE, pc))
  1149.       goto write_error;
  1150.     }
  1151.  
  1152.     return(error_found);
  1153.  
  1154.   write_error:
  1155.     if(style == QStatus)
  1156.       q_status_message1(SM_ORDER, 3, 4, "Error writing message: %s", 
  1157.             error_description(errno));
  1158.  
  1159.     return(1);
  1160. }
  1161.  
  1162.  
  1163.  
  1164.  
  1165.  
  1166. /*------------------------------------------------------------------
  1167.    This list of known escape sequences is taken from RFC's 1486 and 1554
  1168.    and draft-apng-cc-encoding, and the X11R5 source with only a remote
  1169.    understanding of what this all means...
  1170.  
  1171.    NOTE: if the length of these should extend beyond 4 chars, fix
  1172.      MAX_ESC_LEN in filter.c
  1173.   ----*/
  1174. static char *known_escapes[] = {
  1175.     "(B",  "(J",  "$@",  "$B",            /* RFC 1468 */
  1176.     "$A",  "$(C", "$(D", ".A",  ".F",        /* added by RFC 1554 */
  1177.     "$)C", "$)A", "$*E", "$*X",            /* those in apng-draft */
  1178.     "$+G", "$+H", "$+I", "$+J", "$+K",
  1179.     "$+L", "$+M",
  1180.     ")I",   "-A",  "-B",  "-C",  "-D",        /* codes form X11R5 source */
  1181.     "-F",   "-G",  "-H",   "-L",  "-M",
  1182.     "-$(A", "$(B", "$)B", "$)D",
  1183.     NULL};
  1184.  
  1185. int
  1186. match_escapes(esc_seq)
  1187.     char *esc_seq;
  1188. {
  1189.     char **p;
  1190.     int    n;
  1191.  
  1192.     for(p = known_escapes; *p && strncmp(esc_seq, *p, n = strlen(*p)); p++)
  1193.       ;
  1194.  
  1195.     return(*p ? n + 1 : 0);
  1196. }
  1197.  
  1198.  
  1199.  
  1200. /*----------------------------------------------------------------------
  1201.   Format header text suitable for display
  1202.  
  1203.   Args: stream -- mail stream for various header text fetches
  1204.     msgno -- sequence number in stream of message we're interested in
  1205.     env -- pointer to msg's envelope
  1206.     hdrs -- struct containing what's to get formatted
  1207.     pc -- function to write header text with
  1208.     prefix -- prefix to append to each output line
  1209.  
  1210.   Result: 0 if all's well, -1 if write error, 1 if fetch error
  1211.  
  1212.   NOTE: Blank-line delimiter is NOT written here.  Newlines are written
  1213.     in the local convention.
  1214.  
  1215.  ----*/
  1216. #define    FHT_OK        0
  1217. #define    FHT_WRTERR    -1
  1218. #define    FHT_FTCHERR    1
  1219. int
  1220. format_header_text(stream, msgno, env, hdrs, pc, prefix)
  1221.     MAILSTREAM *stream;
  1222.     long    msgno;
  1223.     ENVELOPE   *env;
  1224.     HEADER_S   *hdrs;
  1225.     gf_io_t    pc;
  1226.     char       *prefix;
  1227. {
  1228.     int   rv = FHT_OK;
  1229.     int  nfields, i;
  1230.     char *h = NULL, **fields = NULL, **v, *p, *q, *start,
  1231.      *finish, *current;
  1232.  
  1233.     if(ps_global->full_header)
  1234.       return(format_raw_header(stream, msgno, pc, prefix));
  1235.  
  1236.     /*
  1237.      * First, calculate how big a fields array we need.
  1238.      */
  1239.  
  1240.     /* Custom header viewing list specified */
  1241.     if(hdrs->type == HD_LIST){
  1242.     /* view all these headers */
  1243.     if(!hdrs->except){
  1244.         for(nfields = 0, v = hdrs->h.l; (q = *v) != NULL; v++)
  1245.           if(!is_an_env_hdr(q, env && env->newsgroups))
  1246.         nfields++;
  1247.     }
  1248.     /* view all except these headers */
  1249.     else{
  1250.         for(nfields = 0, v = hdrs->h.l; *v != NULL; v++)
  1251.           nfields++;
  1252.           
  1253.         if(nfields > 1)
  1254.           nfields--; /* subtract one for ALL_EXCEPT field */
  1255.     }
  1256.     }
  1257.     /* default view */
  1258.     else{
  1259.     nfields = 6;
  1260.     if(!(env && env->newsgroups))
  1261.       nfields++;
  1262.     }
  1263.  
  1264.     /* allocate pointer space */
  1265.     if(nfields){
  1266.     fields = (char **)fs_get((size_t)(nfields+1) * sizeof(char *));
  1267.     memset(fields, 0, (size_t)(nfields+1) * sizeof(char *));
  1268.     }
  1269.  
  1270.     if(hdrs->type == HD_LIST){
  1271.     /* view all these headers */
  1272.     if(!hdrs->except){
  1273.         /* put the non-envelope headers in fields */
  1274.         if(nfields)
  1275.           for(i = 0, v = hdrs->h.l; (q = *v) != NULL; v++)
  1276.         if(!is_an_env_hdr(q, env && env->newsgroups))
  1277.           fields[i++] = q;
  1278.     }
  1279.     /* view all except these headers */
  1280.     else{
  1281.         /* put the list of headers not to view in fields */
  1282.         if(nfields)
  1283.           for(i = 0, v = hdrs->h.l; (q = *v) != NULL; v++)
  1284.         if(strucmp(ALL_EXCEPT, q))
  1285.           fields[i++] = q;
  1286.     }
  1287.  
  1288.     v = hdrs->h.l;
  1289.     }
  1290.     else{
  1291.     if(nfields){
  1292.         i = 0;
  1293.         if(!(env && env->newsgroups))
  1294.           fields[i++] = "Newsgroups";
  1295.  
  1296.         fields[i++] = "Resent-Date";
  1297.         fields[i++] = "Resent-From";
  1298.         fields[i++] = "Resent-To";
  1299.         fields[i++] = "Resent-cc";
  1300.         fields[i++] = "Resent-Subject";
  1301.         fields[i++] = "Followup-To";
  1302.     }
  1303.  
  1304.     v = fields;
  1305.     }
  1306.  
  1307.     /* custom view with exception list */
  1308.     if(hdrs->type == HD_LIST && hdrs->except){
  1309.     /*
  1310.      * Go through each header in h and print it.
  1311.      * First we check to see if it is an envelope header so we
  1312.      * can print our envelope version of it instead of the raw version.
  1313.      */
  1314.  
  1315.     /* fetch all the other headers */
  1316.     if(nfields)
  1317.       h = xmail_fetchheader_lines_not(stream, msgno, fields);
  1318.  
  1319.     for(current = h;
  1320.         h && delineate_this_header(h,NULL,current,&start,&finish);
  1321.         current = finish){
  1322.         char tmp[MAILTMPLEN+1];
  1323.         char *colon_loc;
  1324.  
  1325.         colon_loc = strindex(start, ':');
  1326.         if(colon_loc && colon_loc < finish){
  1327.         strncpy(tmp, start, min(colon_loc-start, MAILTMPLEN));
  1328.         tmp[min(colon_loc-start, MAILTMPLEN)] = '\0';
  1329.         }
  1330.         else
  1331.           colon_loc = NULL;
  1332.  
  1333.         if(colon_loc && is_an_env_hdr(tmp, env && env->newsgroups)){
  1334.         /* pretty format for env hdrs */
  1335.         format_env_hdr(stream, msgno, env, pc, tmp, prefix);
  1336.         }
  1337.         else{
  1338.         if(rv = format_raw_hdr_string(start, finish, pc, prefix))
  1339.           goto write_error;
  1340.         else
  1341.           start = finish;
  1342.         }
  1343.     }
  1344.     }
  1345.     /* custom view or default */
  1346.     else{
  1347.     /* fetch the non-envelope headers */
  1348.     if(nfields)
  1349.       h = xmail_fetchheader_lines(stream, msgno, fields);
  1350.  
  1351.     /* default envelope for default view */
  1352.     if(hdrs->type == HD_BFIELD)
  1353.       format_envelope(stream, msgno, env, pc, hdrs->h.b, prefix);
  1354.  
  1355.     /* go through each header in list, v initialized above */
  1356.     for(; q = *v; v++){
  1357.         if(is_an_env_hdr(q, env && env->newsgroups)){
  1358.         /* pretty format for env hdrs */
  1359.         format_env_hdr(stream, msgno, env, pc, q, prefix);
  1360.         }
  1361.         else{
  1362.         /*
  1363.          * Go through h finding all occurences of this header
  1364.          * and all continuation lines, and output.
  1365.          */
  1366.         for(current = h;
  1367.             h && delineate_this_header(h,q,current,&start,&finish);
  1368.             current = finish){
  1369.             if(rv = format_raw_hdr_string(start, finish, pc, prefix))
  1370.               goto write_error;
  1371.             else
  1372.               start = finish;
  1373.         }
  1374.         }
  1375.     }
  1376.     }
  1377.  
  1378.   write_error:
  1379.  
  1380.     if(h)
  1381.       fs_give((void **)&h);
  1382.  
  1383.     if(fields)
  1384.       fs_give((void **)&fields);
  1385.  
  1386.     return(rv);
  1387. }
  1388.  
  1389.  
  1390.  
  1391. /*----------------------------------------------------------------------
  1392.   Format RAW header text for display
  1393.  
  1394.   Args: stream --
  1395.     msgno --
  1396.     pc --
  1397.     prefix --
  1398.  
  1399.   Result: 0 if all's well, -1 if write error, 1 if fetch error
  1400.  
  1401.   NOTE: Blank-line delimiter is NOT written here.  Newlines are written
  1402.     in the local convention.
  1403.  
  1404.  ----*/
  1405. int
  1406. format_raw_header(stream, msgno, pc, prefix)
  1407.     MAILSTREAM *stream;
  1408.     long    msgno;
  1409.     gf_io_t    pc;
  1410.     char       *prefix;
  1411. {
  1412.     char *h = mail_fetchheader(stream, msgno);
  1413.  
  1414.     if(h){
  1415.     if(prefix && !gf_puts(prefix, pc))
  1416.       return(FHT_WRTERR);
  1417.  
  1418.     while(*h){
  1419.         if(ISRFCEOL(h)){
  1420.         h += 2;
  1421.         if(!gf_puts(NEWLINE, pc))
  1422.           return(FHT_WRTERR);
  1423.  
  1424.         if(ISRFCEOL(h))        /* all done! */
  1425.           return(FHT_OK);
  1426.  
  1427.         if(prefix && !gf_puts(prefix, pc))
  1428.           return(FHT_WRTERR);
  1429.         }
  1430.         else if(F_OFF(F_PASS_CONTROL_CHARS, ps_global) && CAN_DISPLAY(*h)){
  1431.         if(!((*pc)('^') && (*pc)(*h++ + '@')))
  1432.           return(FHT_WRTERR);
  1433.         }
  1434.         else if(!(*pc)(*h++))
  1435.           return(FHT_WRTERR);
  1436.     }
  1437.     }
  1438.     else
  1439.       return(FHT_FTCHERR);
  1440.  
  1441.     return(FHT_OK);
  1442. }
  1443.  
  1444.  
  1445.  
  1446. /*----------------------------------------------------------------------
  1447.   Format c-client envelope data suitable for display
  1448.  
  1449.   Args: stream -- stream associated with this envelope
  1450.     msgno -- message number associated with this envelope
  1451.     e -- envelope
  1452.     pc -- place to write result
  1453.     which -- which header lines to write
  1454.     prefix -- string to write before each header line
  1455.  
  1456.   Result: 0 if all's well, -1 if write error, 1 if fetch error
  1457.  
  1458.   NOTE: Blank-line delimiter is NOT written here.  Newlines are written
  1459.     in the local convention.
  1460.  
  1461.  ----*/
  1462. void
  1463. format_envelope(s, n, e, pc, which, prefix)
  1464.     MAILSTREAM *s;
  1465.     long    n;
  1466.     ENVELOPE   *e;
  1467.     gf_io_t     pc;
  1468.     long    which;
  1469.     char       *prefix;
  1470. {
  1471.     if(!e)
  1472.       return;
  1473.  
  1474.     if((which & FE_DATE) && e->date) {
  1475.     if(prefix)
  1476.       gf_puts(prefix, pc);
  1477.  
  1478.     gf_puts("Date: ", pc);
  1479.     format_env_puts(e->date, pc);
  1480.     gf_puts(NEWLINE, pc);
  1481.     }
  1482.  
  1483.     if((which & FE_FROM) && e->from)
  1484.       format_addr_string(s, n, "From: ", e->from, prefix, pc);
  1485.  
  1486.     if((which & FE_REPLYTO) && e->reply_to
  1487.        && (!e->from || !address_is_same(e->reply_to, e->from)))
  1488.       format_addr_string(s, n, "Reply-To: ", e->reply_to, prefix, pc);
  1489.  
  1490.     if((which & FE_TO) && e->to)
  1491.       format_addr_string(s, n, "To: ", e->to, prefix, pc);
  1492.  
  1493.     if((which & FE_CC) && e->cc)
  1494.       format_addr_string(s, n, "Cc: ", e->cc, prefix, pc);
  1495.  
  1496.     if((which & FE_BCC) && e->bcc)
  1497.       format_addr_string(s, n, "Bcc: ", e->bcc, prefix, pc);
  1498.  
  1499.     if((which & FE_NEWSGROUPS) && e->newsgroups && !ps_global->nr_mode)
  1500.       format_newsgroup_string("Newsgroups: ", e->newsgroups, prefix, pc);
  1501.  
  1502.     if((which & FE_SUBJECT) && e->subject && e->subject[0]){
  1503.     if(prefix)
  1504.       gf_puts(prefix, pc);
  1505.  
  1506.     gf_puts("Subject: ", pc);
  1507.     format_env_puts((char *) rfc1522_decode((unsigned char *) tmp_20k_buf,
  1508.                         e->subject, NULL), pc);
  1509.     gf_puts(NEWLINE, pc);
  1510.     }
  1511.  
  1512.     if((which & FE_SENDER) && e->sender
  1513.        && (!e->from || !address_is_same(e->sender, e->from)))
  1514.       format_addr_string(s, n, "Sender: ", e->sender, prefix, pc);
  1515.  
  1516.     if((which & FE_MESSAGEID) && e->message_id){
  1517.     if(prefix)
  1518.       gf_puts(prefix, pc);
  1519.  
  1520.     gf_puts("Message-ID: ", pc);
  1521.     format_env_puts((char *) rfc1522_decode((unsigned char *) tmp_20k_buf,
  1522.                         e->message_id, NULL), pc);
  1523.     gf_puts(NEWLINE, pc);
  1524.     }
  1525.  
  1526.     if((which & FE_INREPLYTO) && e->in_reply_to){
  1527.     if(prefix)
  1528.       gf_puts(prefix, pc);
  1529.  
  1530.     gf_puts("In-Reply-To: ", pc);
  1531.     format_env_puts((char *) rfc1522_decode((unsigned char *) tmp_20k_buf,
  1532.                         e->in_reply_to, NULL), pc);
  1533.     gf_puts(NEWLINE, pc);
  1534.     }
  1535. }
  1536.  
  1537.  
  1538.  
  1539. /*----------------------------------------------------------------------
  1540.      Format an address field, wrapping lines nicely at commas
  1541.  
  1542.   Args: field_name  -- The name of the field we're formatting ("TO: ", ...)
  1543.         addr        -- ADDRESS structure to format
  1544.         line_prefix -- A prefix string for each line such as "> "
  1545.  
  1546.  Result: A formatted, malloced string is returned.
  1547.  
  1548. The resulting lines formatted are 80 columns wide.
  1549.   ----------------------------------------------------------------------*/
  1550. void
  1551. format_addr_string(stream, msgno, field_name, addr, line_prefix, pc)
  1552.     MAILSTREAM *stream;
  1553.     long    msgno;
  1554.     ADDRESS    *addr;
  1555.     char       *line_prefix, *field_name;
  1556.     gf_io_t    pc;
  1557. {
  1558.     char     buf[MAILTMPLEN], *ptmp, *mtmp;
  1559.     int         trailing = 0, was_start_of_group = 0, llen, alen, plen = 0;
  1560.     ADDRESS *atmp;
  1561.  
  1562.     if(!addr)
  1563.       return;
  1564.  
  1565.     if(line_prefix)
  1566.       gf_puts(line_prefix, pc);
  1567.  
  1568.     /*
  1569.      * quickly run down address list to make sure none are patently bogus.
  1570.      * If so, just blat raw field out.
  1571.      */
  1572.     for(atmp = addr; stream && atmp; atmp = atmp->next)
  1573.       if(atmp->host && atmp->host[0] == '.'){
  1574.       char *field, *fields[2];
  1575.  
  1576.       fields[1] = NULL;
  1577.       fields[0] = cpystr(field_name);
  1578.       if(ptmp = strchr(fields[0], ':'))
  1579.         *ptmp = '\0';
  1580.  
  1581.       if(field = xmail_fetchheader_lines(stream, msgno, fields)){
  1582.           char *h, *t;
  1583.  
  1584.           for(t = h = field; *h ; t++)
  1585.         if(*t == '\015' && *(t+1) == '\012'){
  1586.             *t = '\0';            /* tie off line */
  1587.             format_env_puts(h, pc);
  1588.             if(*(h = (++t) + 1)){    /* set new h and skip CRLF */
  1589.             gf_puts(NEWLINE, pc);    /* more to write */
  1590.             if(line_prefix)
  1591.               gf_puts(line_prefix, pc);
  1592.             }
  1593.             else
  1594.               break;
  1595.         }
  1596.         else if(!*t){            /* shouldn't happen much */
  1597.             if(h != t)
  1598.               format_env_puts(h, pc);
  1599.  
  1600.             break;
  1601.         }
  1602.  
  1603.           fs_give((void **)&field);
  1604.       }
  1605.  
  1606.       fs_give((void **)&fields[0]);
  1607.       gf_puts(NEWLINE, pc);
  1608.       q_status_message1(SM_ORDER, 0, 3, "Error in \"%s\" field address",
  1609.                 field_name);
  1610.       return;
  1611.       }
  1612.  
  1613.     gf_puts(field_name, pc);
  1614.  
  1615.     if(line_prefix)
  1616.       plen = strlen(line_prefix);
  1617.  
  1618.     llen = plen + strlen(field_name);
  1619.     while(addr){
  1620.     atmp           = addr->next;        /* remember what's next */
  1621.     addr->next     = NULL;
  1622.     if(!addr->host && addr->mailbox){
  1623.         mtmp = addr->mailbox;
  1624.         addr->mailbox = cpystr((char *)rfc1522_decode(
  1625.                (unsigned char *)tmp_20k_buf, addr->mailbox, NULL));
  1626.     }
  1627.  
  1628.     ptmp           = addr->personal;    /* RFC 1522 personal name? */
  1629.     addr->personal = (char *) rfc1522_decode((unsigned char *)tmp_20k_buf,
  1630.                          addr->personal, NULL);
  1631.     buf[0]           = '\0';
  1632.     rfc822_write_address(buf, addr);    /* write address into buf */
  1633.     alen           = strlen(buf);
  1634.     addr->personal = ptmp;            /* restore old personal ptr */
  1635.     if(!addr->host && addr->mailbox){
  1636.         fs_give((void **)&addr->mailbox);
  1637.         addr->mailbox = mtmp;
  1638.     }
  1639.  
  1640.     if(!trailing){                /* 1st pass, just address */
  1641.         llen += alen;
  1642.         trailing++;
  1643.     }
  1644.     else{                    /* else comma, unless */
  1645.         if(!((was_start_of_group && addr->host)  /* 1st addr in group, */
  1646.            || (!addr->host && !addr->mailbox))){ /* or end of group */
  1647.         gf_puts(",", pc);
  1648.         llen++;
  1649.         }
  1650.  
  1651.         if(alen + llen + 1 > 76){
  1652.         gf_puts(NEWLINE, pc);
  1653.         if(line_prefix)
  1654.           gf_puts(line_prefix, pc);
  1655.  
  1656.         gf_puts("    ", pc);
  1657.         llen = alen + plen + 5;
  1658.         }
  1659.         else{
  1660.         gf_puts(" ", pc);
  1661.         llen += alen + 1;
  1662.         }
  1663.     }
  1664.  
  1665.     if(alen && llen > 76){        /* handle long addresses */
  1666.         register char *q, *p = &buf[alen-1];
  1667.  
  1668.         while(p > buf){
  1669.         if(isspace((unsigned char)*p)
  1670.            && (llen - (alen - (int)(p - buf))) < 76){
  1671.             for(q = buf; q < p; q++)
  1672.               if(F_OFF(F_PASS_CONTROL_CHARS, ps_global)
  1673.              && CAN_DISPLAY(*q)){
  1674.               (*pc)('^');
  1675.               (*pc)(*q + '@');
  1676.               }
  1677.               else
  1678.             (*pc)(*q);
  1679.  
  1680.             gf_puts(NEWLINE, pc);
  1681.             gf_puts("    ", pc);
  1682.             format_env_puts(p, pc);
  1683.             break;
  1684.         }
  1685.         else
  1686.           p--;
  1687.         }
  1688.  
  1689.         if(p == buf)        /* no reasonable break point */
  1690.           format_env_puts(buf, pc);
  1691.     }
  1692.     else
  1693.       format_env_puts(buf, pc);
  1694.  
  1695.     if(!addr->host && addr->mailbox)
  1696.       was_start_of_group = 1;
  1697.     else
  1698.       was_start_of_group = 0;
  1699.  
  1700.     addr->next = atmp;
  1701.     addr       = atmp;
  1702.     }
  1703.  
  1704.     gf_puts(NEWLINE, pc);
  1705. }
  1706.  
  1707.  
  1708.  
  1709. /*----------------------------------------------------------------------
  1710.   Format an address field, wrapping lines nicely at commas
  1711.  
  1712.   Args: field_name  -- The name of the field we're formatting ("TO:", Cc:...)
  1713.         newsgrps    -- ADDRESS structure to format
  1714.         line_prefix -- A prefix string for each line such as "> "
  1715.  
  1716.   Result: A formatted, malloced string is returned.
  1717.  
  1718. The resuling lines formatted are 80 columns wide.
  1719.   ----------------------------------------------------------------------*/
  1720. void
  1721. format_newsgroup_string(field_name, newsgrps, line_prefix, pc)
  1722.     char    *newsgrps;
  1723.     char    *line_prefix, *field_name;
  1724.     gf_io_t  pc;
  1725. {
  1726.     char     buf[MAILTMPLEN];
  1727.     int         trailing = 0, llen, alen, plen = 0;
  1728.     char    *next_ng;
  1729.     
  1730.     if(!newsgrps || !*newsgrps)
  1731.       return;
  1732.     
  1733.     if(line_prefix)
  1734.       gf_puts(line_prefix, pc);
  1735.  
  1736.     gf_puts(field_name, pc);
  1737.  
  1738.     if(line_prefix)
  1739.       plen = strlen(line_prefix);
  1740.  
  1741.     llen = plen + strlen(field_name);
  1742.     while(*newsgrps){
  1743.         for(next_ng = newsgrps; *next_ng && *next_ng != ','; next_ng++);
  1744.         strncpy(buf, newsgrps, next_ng - newsgrps);
  1745.         buf[next_ng - newsgrps] = '\0';
  1746.         newsgrps = next_ng;
  1747.         if(*newsgrps)
  1748.           newsgrps++;
  1749.     alen = strlen(buf);
  1750.     if(!trailing){            /* first time thru, just address */
  1751.         llen += alen;
  1752.         trailing++;
  1753.     }
  1754.     else{                /* else preceding comma */
  1755.         gf_puts(",", pc);
  1756.         llen++;
  1757.  
  1758.         if(alen + llen + 1 > 76){
  1759.         gf_puts(NEWLINE, pc);
  1760.         if(line_prefix)
  1761.           gf_puts(line_prefix, pc);
  1762.  
  1763.         gf_puts("    ", pc);
  1764.         llen = alen + plen + 5;
  1765.         }
  1766.         else{
  1767.         gf_puts(" ", pc);
  1768.         llen += alen + 1;
  1769.         }
  1770.     }
  1771.  
  1772.     if(alen && llen > 76){        /* handle long addresses */
  1773.         register char *q, *p = &buf[alen-1];
  1774.  
  1775.         while(p > buf){
  1776.         if(isspace((unsigned char)*p)
  1777.            && (llen - (alen - (int)(p - buf))) < 76){
  1778.             for(q = buf; q < p; q++)
  1779.               (*pc)(*q);    /* write character */
  1780.  
  1781.             gf_puts(NEWLINE, pc);
  1782.             gf_puts("    ", pc);
  1783.             gf_puts(p, pc);
  1784.             break;
  1785.         }
  1786.         else
  1787.           p--;
  1788.         }
  1789.  
  1790.         if(p == buf)        /* no reasonable break point */
  1791.           gf_puts(buf, pc);
  1792.     }
  1793.     else
  1794.       gf_puts(buf, pc);
  1795.     }
  1796.  
  1797.     gf_puts(NEWLINE, pc);
  1798. }
  1799.  
  1800.  
  1801.  
  1802. /*----------------------------------------------------------------------
  1803.   Format a text field that's part of some raw (non-envelope) message header
  1804.  
  1805.   Args: start --
  1806.         finish --
  1807.     pc -- 
  1808.     prefix --
  1809.  
  1810.   Result: Semi-digested text (RFC 1522 decoded, anyway) written with "pc"
  1811.  
  1812.   ----------------------------------------------------------------------*/
  1813. int
  1814. format_raw_hdr_string(start, finish, pc, prefix)
  1815.     char    *start;
  1816.     char    *finish;
  1817.     gf_io_t  pc;
  1818.     char    *prefix;
  1819. {
  1820.     register char *current;
  1821.     char     ch;
  1822.  
  1823.     ch = *finish;
  1824.     *finish = '\0';
  1825.     current = (char *) rfc1522_decode((unsigned char *)tmp_20k_buf,start,NULL);
  1826.     if(islower((unsigned char)(*start)))
  1827.       *start = toupper((unsigned char)(*start));
  1828.  
  1829.     if(prefix && !gf_puts(prefix, pc))
  1830.       return(FHT_WRTERR);
  1831.  
  1832.     /* output from start to finish */
  1833.     while(*current)
  1834.       if(ISRFCEOL(current)){
  1835.       current += 2;
  1836.  
  1837.       if(!gf_puts(NEWLINE, pc)
  1838.          || (*current && prefix && !gf_puts(prefix, pc)))
  1839.         return(FHT_WRTERR);
  1840.       }
  1841.       else if(F_OFF(F_PASS_CONTROL_CHARS, ps_global) && CAN_DISPLAY(*current)){
  1842.       if(!((*pc)('^') && (*pc)(*current++ + '@')))
  1843.         return(FHT_WRTERR);
  1844.       }
  1845.       else if(!(*pc)(*current++))
  1846.     return(FHT_WRTERR);
  1847.  
  1848.     *finish = ch;
  1849.     return(FHT_OK);
  1850. }
  1851.  
  1852.  
  1853.  
  1854.  
  1855. /*----------------------------------------------------------------------
  1856.   Format a text field that's part of some raw (non-envelope) message header
  1857.  
  1858.   Args: s --
  1859.     pc -- 
  1860.  
  1861.   Result: Output
  1862.  
  1863.   ----------------------------------------------------------------------*/
  1864. int
  1865. format_env_puts(s, pc)
  1866.     char    *s;
  1867.     gf_io_t  pc;
  1868. {
  1869.     if(F_ON(F_PASS_CONTROL_CHARS, ps_global))
  1870.       return(gf_puts(s, pc));
  1871.  
  1872.     for(; *s; s++)
  1873.       if(CAN_DISPLAY(*s)){
  1874.       if(!((*pc)('^') && (*pc)(*s + '@')))
  1875.         return(0);
  1876.       }
  1877.       else if(!(*pc)(*s))
  1878.     return(0);
  1879.  
  1880.     return(1);
  1881. }
  1882.  
  1883.  
  1884.  
  1885.  
  1886. /*----------------------------------------------------------------------
  1887.     Format a strings describing one unshown part of a Mime message
  1888.  
  1889. Args: number -- A string with the part number i.e. "3.2.1"
  1890.       body   -- The body part
  1891.       type   -- 1 - Not shown, but can be
  1892.                 2 - Not shown, cannot be shown
  1893.                 3 - Can't print
  1894.  
  1895.  
  1896. Result: pointer to formatted string in static buffer
  1897.  
  1898. Note that size of the strings are carefully calculated never to overflow 
  1899. the static buffer:
  1900.     number  < 20,  description limited to 100, type_desc < 200,
  1901.     size    < 20,  second line < 100           other stuff < 60
  1902.  ----*/
  1903. char *
  1904. part_desc(number, body, type)
  1905.      BODY *body;
  1906.      int type;
  1907.      char *number;
  1908. {
  1909.     char *t;
  1910.  
  1911.     sprintf(tmp_20k_buf, "%s  [Part %s, %s%.100s%s%s  %s%s]%s",
  1912.         NEWLINE,
  1913.             number,
  1914.             body->description == NULL ? "" : "\"",
  1915.             body->description == NULL ? "" : body->description,
  1916.             body->description == NULL ? "" : "\"  ",
  1917.             type_desc(body->type, body->subtype, body->parameter, 1),
  1918.             body->type == TYPETEXT ? comatose(body->size.lines) :
  1919.                                      byte_string(body->size.bytes),
  1920.             body->type == TYPETEXT ? " lines" : "",
  1921.         NEWLINE);
  1922.  
  1923.     t = &tmp_20k_buf[strlen(tmp_20k_buf)];
  1924.  
  1925.     switch(type) {
  1926.       case 1:
  1927.         sstrcpy(&t,
  1928.         "  [Not Shown. Use the \"V\" command to view or save this part]");
  1929.     sstrcpy(&t, NEWLINE);
  1930.         break;
  1931.  
  1932.       case 2:
  1933.     sstrcpy(&t, "  [Cannot ");
  1934.     if(body->type != TYPEAUDIO && body->type != TYPEVIDEO)
  1935.       sstrcpy(&t, "dis");
  1936.  
  1937.     sstrcpy(&t, 
  1938.         "play this part. Press \"V\" then \"S\" to save in a file]");
  1939.     sstrcpy(&t, NEWLINE);
  1940.         break;
  1941.  
  1942.       case 3:
  1943.         sstrcpy(&t, "  [Unable to print this part]");
  1944.     sstrcpy(&t, NEWLINE);
  1945.         break;
  1946.     }
  1947.  
  1948.     return(tmp_20k_buf);
  1949. }
  1950.  
  1951.  
  1952. /*
  1953.  * This could be better.  For example, look at the first two.  The
  1954.  * thing that keeps us from using the same array is that the column number
  1955.  * is stored with each entry and it could be different for each.
  1956.  */
  1957. static struct key help_keys[] =
  1958.        {{"M","Main Menu",KS_MAINMENU},    {NULL,NULL,KS_NONE},
  1959.     {"E","Exit Help",KS_EXITMODE},    {NULL,NULL,KS_NONE},
  1960.     {NULL,NULL,KS_NONE},        {NULL,NULL,KS_NONE},
  1961.     {"-","PrevPage",KS_PREVPAGE},    {"Spc","NextPage",KS_NEXTPAGE},
  1962.     {"Y","prYnt",KS_PRINT},        {"Z","Print All",KS_NONE},
  1963.     {"B","Report Bug",KS_NONE},    {"W","WhereIs",KS_WHEREIS}};
  1964. INST_KEY_MENU(help_keymenu, help_keys);
  1965. #define    HLP_MAIN_KEY    0
  1966. #define    HLP_ALL_KEY    9
  1967. #define    HLP_BUG_KEY    10
  1968.  
  1969. static struct key review_keys[] =
  1970.        {{NULL,NULL,KS_NONE},        {NULL,NULL,KS_NONE},
  1971.     {"E","Exit",KS_EXITMODE},    {NULL,NULL,KS_NONE},
  1972.     {NULL,NULL,KS_NONE},        {NULL,NULL,KS_NONE},
  1973.     {"-","PrevPage",KS_PREVPAGE},    {"Spc","NextPage",KS_NEXTPAGE},
  1974.     {"Y","prYnt",KS_PRINT},        {NULL,NULL,KS_NONE},
  1975.     {NULL,NULL,KS_NONE},        {"W","WhereIs",KS_WHEREIS}};
  1976. INST_KEY_MENU(review_keymenu, review_keys);
  1977.  
  1978. static struct key view_keys[] = 
  1979.        {{"?","Help",KS_SCREENHELP},    {"O","OTHER CMDS",KS_NONE},
  1980.     {"M","Main Menu",KS_MAINMENU},    {"V","ViewAttch",KS_VIEW},
  1981.     {"P","PrevMsg",KS_PREVMSG},    {"N","NextMsg",KS_NEXTMSG},
  1982.     {"-","PrevPage",KS_PREVPAGE},    {"Spc","NextPage",KS_NEXTPAGE},
  1983.     {"D","Delete",KS_DELETE},    {"U","Undelete",KS_UNDELETE},
  1984.     {"R","Reply",KS_REPLY},        {"F","Forward",KS_FORWARD},
  1985.  
  1986.     {"?","Help",KS_SCREENHELP},    {"O","OTHER CMDS",KS_NONE},
  1987.     {"Q","Quit",KS_EXIT},        {"C","Compose",KS_COMPOSER},
  1988.     {"L","ListFldrs",KS_FLDRLIST},    {"G","GotoFldr",KS_GOTOFLDR},
  1989.     {"I","Index",KS_FLDRINDEX},    {"W","WhereIs",KS_WHEREIS},
  1990.     {"Y","prYnt",KS_PRINT},        {"T","TakeAddr",KS_TAKEADDR},
  1991.     {"S","Save",KS_SAVE},        {"E","Export",KS_EXPORT},
  1992.  
  1993.     {"?","Help",KS_SCREENHELP},    {"O","OTHER CMDS",KS_NONE},
  1994.     {NULL,NULL,KS_NONE},        {NULL,NULL,KS_NONE},
  1995.     {NULL,NULL,KS_NONE},        {NULL,NULL,KS_NONE},
  1996.     {"J","Jump",KS_JUMPTOMSG},    {"TAB","NextNew",KS_NONE},
  1997.     {"H","HdrMode",KS_HDRMODE},    {"B","Bounce",KS_BOUNCE},
  1998.     {"*","Flag",KS_FLAG},        {"|","Pipe",KS_NONE}};
  1999. INST_KEY_MENU(view_keymenu, view_keys);
  2000. #define VIEW_FULL_HEADERS_KEY 32
  2001. #define BOUNCE_KEY 33
  2002. #define FLAG_KEY 34
  2003. #define VIEW_PIPE_KEY 35
  2004.  
  2005. static struct key nr_anon_view_keys[] = 
  2006.        {{"?","Help",KS_SCREENHELP},    {"W","WhereIs",KS_WHEREIS},
  2007.     {"Q", "Quit",KS_EXIT},        {NULL,NULL,KS_NONE},
  2008.     {"P","PrevMsg",KS_PREVMSG},    {"N","NextMsg",KS_NEXTMSG},
  2009.     {"-","PrevPage",KS_PREVPAGE},    {"Spc","NextPage",KS_NEXTPAGE},
  2010.     {"F","Fwd Email",KS_FORWARD},    {"J","Jump",KS_JUMPTOMSG},
  2011.     {"I", "Index",KS_FLDRINDEX},    {NULL, NULL,KS_NONE}};
  2012. INST_KEY_MENU(nr_anon_view_keymenu, nr_anon_view_keys);
  2013.  
  2014. static struct key nr_view_keys[] = 
  2015.        {{"?","Help",KS_SCREENHELP},    {"O","OTHER CMDS",KS_NONE},
  2016.     {"Q","Quit",KS_EXIT},        {NULL,NULL,KS_NONE},
  2017.     {"P","PrevMsg",KS_PREVMSG},    {"N","NextMsg",KS_NEXTMSG},
  2018.     {"-","PrevPage",KS_PREVPAGE},    {"Spc","NextPage",KS_NEXTPAGE},
  2019.     {"F","Fwd Email",KS_FORWARD},    {"J","Jump",KS_JUMPTOMSG},
  2020.     {"Y","prYnt",KS_PRINT},        {"S","Save",KS_SAVE},
  2021.  
  2022.     {"?","Help",KS_SCREENHELP},    {"O","OTHER CMDS",KS_NONE},
  2023.     {"E","Export",KS_EXPORT},    {"C","Compose",KS_COMPOSER},
  2024.     {NULL,NULL,KS_NONE},        {NULL,NULL,KS_NONE},
  2025.     {"I","Index",KS_FLDRINDEX},    {"W","WhereIs",KS_WHEREIS},
  2026.     {NULL,NULL,KS_NONE},        {NULL,NULL,KS_NONE},
  2027.     {NULL,NULL,KS_NONE},        {NULL,NULL,KS_NONE}};
  2028. INST_KEY_MENU(nr_view_keymenu, nr_view_keys);
  2029.  
  2030. static struct key text_att_view_keys[] =
  2031.        {{"?","Help",KS_SCREENHELP},    {NULL,NULL,KS_NONE},
  2032.     {"E","Exit Viewer",KS_EXITMODE},    {NULL,NULL,KS_NONE},
  2033.     {NULL,NULL,KS_NONE},        {NULL,NULL,KS_NONE},
  2034.     {"-","PrevPage",KS_PREVPAGE},    {"Spc","NextPage",KS_NEXTPAGE},
  2035.     {"Y","prYnt",KS_PRINT},        {"S","Save",KS_SAVE},
  2036.     {"|","Pipe",KS_NONE},        {"W", "WhereIs",KS_WHEREIS}};
  2037. INST_KEY_MENU(text_att_view_keymenu, text_att_view_keys);
  2038. #define ATT_SAVE_KEY 9
  2039. #define ATT_PIPE_KEY 10
  2040.  
  2041.  
  2042. static struct key simple_view_keys[] =
  2043.        {{"?","Help",KS_SCREENHELP},    {NULL,NULL,KS_NONE},
  2044.     {"Q","Quit Viewer",KS_NONE},    {NULL,NULL,KS_NONE},
  2045.     {NULL,NULL,KS_NONE},        {NULL,NULL,KS_NONE},
  2046.     {"-","PrevPage",KS_PREVPAGE},    {"Spc","NextPage",KS_NEXTPAGE},
  2047.     {"F","Fwd Email",KS_FORWARD},    {"S","Save",KS_SAVE},
  2048.     {NULL,NULL,KS_NONE},        {"W","WhereIs",KS_WHEREIS}};
  2049. INST_KEY_MENU(simple_view_keymenu, simple_view_keys);
  2050. #define SAVE_KEY 9
  2051.  
  2052. #define    STYLE_NAME(s)    (((s) == HelpText || (s) == ComposerHelpText) ? "help"\
  2053.               : ((s) == MessageText) ? "message"\
  2054.               : ((s) == ViewAbookText) ? "expanded entry"\
  2055.               : ((s) == ViewAbookAtt) ? "attachment" : "text")
  2056.  
  2057.  
  2058. /*----------------------------------------------------------------------
  2059.    routine for displaying help and message text on the screen.
  2060.  
  2061.   Args: text          buffer to display
  2062.         title         string with title of text being displayed
  2063.         style         whether we are display a message, help text ...
  2064.     source          what's text: char **, char * or FILE * ???
  2065.  
  2066.  
  2067.    This displays in three different kinds of text. One is an array of
  2068. lines passed in in text_array. The other is a simple long string of
  2069. characters passed in in text. The simple string of characters may have
  2070. another string, the header which is prepended to the text with some
  2071. special processing. The two header.... args specify how format and
  2072. filter the header.
  2073.  
  2074.   The style determines what some of the error messages will be, and
  2075. what commands are available as different things are appropriate for
  2076. help text than for message text etc.
  2077.  
  2078.  ---*/
  2079.  
  2080. void
  2081. scrolltool(text, title, style, source, att)
  2082.     void       *text;
  2083.     char       *title;
  2084.     TextType    style;        /* message, news, etc. */
  2085.     SourceType  source;        /* char **, char * or FILE * */
  2086.     ATTACH_S   *att;        /* used only with style AttachText */
  2087. {
  2088.     register long    cur_top_line,  num_display_lines;
  2089.     int              result, done, ch, found_on, found_on_col,
  2090.              orig_ch, first_view, force, scroll_lines, km_size,
  2091.              cursor_row, cursor_col, km_popped;
  2092.     struct key_menu *km;
  2093.     bitmap_t         bitmap;
  2094.     OtherMenu        what;
  2095.     Pos             whereis_pos;
  2096.  
  2097.     num_display_lines          = SCROLL_LINES(ps_global);
  2098.     km_popped              = 0;
  2099.     ps_global->mangled_screen = 1;
  2100.  
  2101.     what        = FirstMenu;        /* which key menu to display */
  2102.     cur_top_line    = 0;
  2103.     done        = 0;
  2104.     found_on        = -1;
  2105.     found_on_col    = -1;
  2106.     first_view        = 1;
  2107.     force        = 0;
  2108.     ch            = 'x';            /* for first time through */
  2109.     whereis_pos.row = 0;
  2110.     whereis_pos.col = 0;
  2111.  
  2112.     set_scroll_text(scroll_state(SS_NEW), text, cur_top_line, source, style);
  2113.     format_scroll_text();
  2114.  
  2115.     setbitmap(bitmap);
  2116.     if(style == AttachText) {
  2117.       km = &text_att_view_keymenu;
  2118.       if(!att){
  2119.       clrbitn(ATT_SAVE_KEY, bitmap);
  2120.       clrbitn(ATT_PIPE_KEY, bitmap);
  2121.       }
  2122.       else if(F_OFF(F_ENABLE_PIPE, ps_global))
  2123.     clrbitn(ATT_PIPE_KEY, bitmap);
  2124.     }
  2125.     else if(style == SimpleText) {
  2126.     km = &simple_view_keymenu;
  2127.     if(ps_global->anonymous)
  2128.       clrbitn(SAVE_KEY, bitmap);
  2129.     }
  2130.     else if(ps_global->anonymous) {
  2131.     km = &nr_anon_view_keymenu;
  2132.     }
  2133.     else if(ps_global->nr_mode) {
  2134.     km = &nr_view_keymenu;
  2135.     }
  2136.     else if(style == MessageText) {
  2137.     km = &view_keymenu;
  2138. #ifndef DOS
  2139.     if(F_OFF(F_ENABLE_PIPE,ps_global))
  2140. #endif
  2141.       clrbitn(VIEW_PIPE_KEY, bitmap);    /* always clear for DOS */
  2142.     if(F_OFF(F_ENABLE_BOUNCE,ps_global))
  2143.       clrbitn(BOUNCE_KEY, bitmap);
  2144.     if(F_OFF(F_ENABLE_FLAG,ps_global))
  2145.       clrbitn(FLAG_KEY, bitmap);
  2146.     if(F_OFF(F_ENABLE_FULL_HDR,ps_global))
  2147.       clrbitn(VIEW_FULL_HEADERS_KEY, bitmap);
  2148.     }
  2149.     else if(style == ReviewMsgsText || style == ViewAbookText
  2150.             || style == ViewAbookAtt) {
  2151.     km = &review_keymenu;
  2152.     if(style == ReviewMsgsText){
  2153.         cur_top_line=max(0, get_scroll_text_lines()-(num_display_lines-2));
  2154.         if(F_ON(F_SHOW_CURSOR, ps_global)){
  2155.         whereis_pos.row = get_scroll_text_lines() - cur_top_line;
  2156.         found_on        = get_scroll_text_lines() - 1;
  2157.         }
  2158.     }
  2159.     }
  2160.     else {                    /* must be paging help */
  2161.     km = &help_keymenu;
  2162.     if(style == ComposerHelpText) {        /* composer gets minimum */
  2163.         clrbitn(HLP_MAIN_KEY, bitmap);
  2164.         clrbitn(HLP_BUG_KEY, bitmap);
  2165.     }
  2166.  
  2167.     if(style != MainHelpText)        /* only main can "print all" */
  2168.       clrbitn(HLP_ALL_KEY, bitmap);
  2169.     }
  2170.  
  2171.     cancel_busy_alarm(-1);
  2172.  
  2173.     while(!done) {
  2174.     if(km_popped){
  2175.         km_popped--;
  2176.         if(km_popped == 0){
  2177.         clearfooter(ps_global);
  2178.         ps_global->mangled_body = 1;
  2179.         }
  2180.     }
  2181.  
  2182.     if(ps_global->mangled_screen) {
  2183.         ps_global->mangled_header = 1;
  2184.         ps_global->mangled_footer = 1;
  2185.             ps_global->mangled_body   = 1;
  2186.     }
  2187.  
  2188.         if(streams_died())
  2189.           ps_global->mangled_header = 1;
  2190.  
  2191.         dprint(9, (debugfile, "@@@@ current:%ld\n",
  2192.            mn_get_cur(ps_global->msgmap)));
  2193.  
  2194.  
  2195.         /*==================== All Screen painting ====================*/
  2196.         /*-------------- The title bar ---------------*/
  2197.     update_scroll_titlebar(title, style, cur_top_line,
  2198.                    ps_global->mangled_header);
  2199.  
  2200.     if(ps_global->mangled_screen){
  2201.         /* this is the only line not cleared by header, body or footer
  2202.          * repaint calls....
  2203.          */
  2204.         ClearLine(1);
  2205.             ps_global->mangled_screen = 0;
  2206.     }
  2207.  
  2208.         /*---- Scroll or update the body of the text on the screen -------*/
  2209.         cur_top_line        = scroll_scroll_text(cur_top_line,
  2210.                              ps_global->mangled_body);
  2211.     ps_global->redrawer    = redraw_scroll_text;
  2212.         ps_global->mangled_body = 0;
  2213.  
  2214.         /*------------- The key menu footer --------------------*/
  2215.     if(ps_global->mangled_footer) {
  2216.         if(km_popped){
  2217.         FOOTER_ROWS(ps_global) = 3;
  2218.         clearfooter(ps_global);
  2219.         }
  2220.  
  2221.             draw_keymenu(km, bitmap, ps_global->ttyo->screen_cols,
  2222.         1-FOOTER_ROWS(ps_global),0,what,0);
  2223.         what = SameTwelve;
  2224.         ps_global->mangled_footer = 0;
  2225.         if(km_popped){
  2226.         FOOTER_ROWS(ps_global) = 1;
  2227.         mark_keymenu_dirty();
  2228.         }
  2229.     }
  2230.  
  2231.     /*============ Check for New Mail and CheckPoint ============*/
  2232.         if(new_mail(force, first_view ? 0 : NM_TIMING(ch), 1) >= 0)
  2233.       update_scroll_titlebar(title, style, cur_top_line, 1);
  2234.  
  2235.     if(first_view && num_display_lines >= get_scroll_text_lines())
  2236.       q_status_message1(SM_INFO, 0, 1, "ALL of %s", STYLE_NAME(style));
  2237.  
  2238.     force      = 0;        /* may not need to next time around */
  2239.     first_view = 0;        /* check_point a priority any more? */
  2240.  
  2241.     /*==================== Output the status message ==============*/
  2242.     if(km_popped){
  2243.         FOOTER_ROWS(ps_global) = 3;
  2244.         mark_status_unknown();
  2245.     }
  2246.  
  2247.         display_message(ch);
  2248.     if(km_popped){
  2249.         FOOTER_ROWS(ps_global) = 1;
  2250.         mark_status_unknown();
  2251.     }
  2252.  
  2253.     if(F_ON(F_SHOW_CURSOR, ps_global)){
  2254.         if(whereis_pos.row > 0){
  2255.         cursor_row  = SCROLL_LINES_ABOVE(ps_global)
  2256.                 + whereis_pos.row - 1;
  2257.         cursor_col  = whereis_pos.col;
  2258.         }
  2259.         else{
  2260.         cursor_col = 0;
  2261.         /* first new line of text */
  2262.             cursor_row  = SCROLL_LINES_ABOVE(ps_global) +
  2263.             ((cur_top_line == 0) ? 0 : ps_global->viewer_overlap);
  2264.         }
  2265.     }
  2266.     else{
  2267.         cursor_col = 0;
  2268.         cursor_row = ps_global->ttyo->screen_rows
  2269.                 - SCROLL_LINES_BELOW(ps_global);
  2270.     }
  2271.  
  2272.     MoveCursor(cursor_row, cursor_col);
  2273.  
  2274.     /*================ Get command and validate =====================*/
  2275. #ifdef    _WINDOWS
  2276.     mswin_allowcopy(mswin_readscrollbuf);
  2277.     mswin_setscrollcallback(scroll_scroll_callback);
  2278. #endif
  2279.         ch = read_command();
  2280.         orig_ch = ch;
  2281. #ifdef    _WINDOWS
  2282.     mswin_allowcopy(NULL);
  2283.     mswin_setscrollcallback(NULL);
  2284.     cur_top_line = scroll_state(SS_CUR)->top_text_line;
  2285. #endif
  2286.  
  2287.         if(ch < 0x0100 && isupper((unsigned char)ch))
  2288.       ch = tolower((unsigned char)ch);
  2289.         else if(ch >= PF1 && ch <= PF12 && km->which > 0 && km->which < 3)
  2290.       ch = (km->which == 1) ? PF2OPF(ch) : PF2OOPF(ch);
  2291.  
  2292.     ch = validatekeys(ch);
  2293.  
  2294.     if(km_popped)
  2295.       switch(ch){
  2296.         case NO_OP_IDLE:
  2297.         case NO_OP_COMMAND: 
  2298.         case PF2:
  2299.         case OPF2:
  2300.             case OOPF2:
  2301.         case 'o' :
  2302.         case KEY_RESIZE:
  2303.         case ctrl('L'):
  2304.           km_popped++;
  2305.           break;
  2306.         
  2307.         default:
  2308.           clearfooter(ps_global);
  2309.           break;
  2310.       }
  2311.  
  2312.  
  2313.     /*============= Execute command =======================*/
  2314.         switch(ch){
  2315.  
  2316.             /* ------ Help -------*/
  2317.           case PF1:
  2318.           case OPF1:
  2319.           case OOPF1:
  2320.           case OOOPF1:
  2321.           case '?':
  2322.           case ctrl('G'):
  2323.         if(FOOTER_ROWS(ps_global) == 1 && km_popped == 0){
  2324.         km_popped = 2;
  2325.         ps_global->mangled_footer = 1;
  2326.         break;
  2327.         }
  2328.  
  2329.         whereis_pos.row = 0;
  2330.             if(ps_global->nr_mode || style == SimpleText
  2331.            || style == ReviewMsgsText || style == ViewAbookText
  2332.            || style == ViewAbookAtt) {
  2333.                 q_status_message(SM_ORDER, 0, 5,
  2334.             "No help text currently available");
  2335.                 break;
  2336.             }
  2337.             if(ch == PF1) {
  2338.             if(style == HelpText || style == MainHelpText)
  2339.                   goto df;
  2340.         if(style == ComposerHelpText)
  2341.                   goto unknown;
  2342.         }
  2343.             if(style == HelpText || style == MainHelpText
  2344.            || style == ComposerHelpText) {
  2345.                 q_status_message(SM_ORDER, 0, 5, "Already in Help");
  2346.         break;
  2347.         }
  2348.  
  2349.         km_size = FOOTER_ROWS(ps_global);
  2350.             if(style == AttachText)
  2351.           helper(h_mail_text_att_view, "HELP FOR ATTACHED TEXT VIEW", 0);
  2352.         else
  2353.           helper(h_mail_view, "HELP FOR MESSAGE TEXT VIEW", 0);
  2354.  
  2355.         if(ps_global->next_screen != main_menu_screen
  2356.            && km_size == FOOTER_ROWS(ps_global)) {
  2357.         /* Have to reset because helper uses scroll_text */
  2358.         num_display_lines      = SCROLL_LINES(ps_global);
  2359.         ps_global->mangled_screen = 1;
  2360.         }
  2361.         else
  2362.           done = 1;
  2363.  
  2364.             break; 
  2365.  
  2366.  
  2367.             /*---------- Roll keymenu ------*/
  2368.           case PF2:
  2369.           case OPF2:
  2370.           case OOPF2:
  2371.       case 'o':
  2372.         if(ps_global->anonymous && ch == PF2)
  2373.           goto whereis;
  2374.         if(km->how_many == 1)
  2375.           goto unknown;
  2376.             if (ch == 'o')
  2377.           warn_other_cmds();
  2378.         what = NextTwelve;
  2379.         ps_global->mangled_footer = 1;
  2380.         break;
  2381.             
  2382.  
  2383.             /* -------- Scroll back one page -----------*/
  2384.           case PF7:
  2385.           case '-':   
  2386.           case ctrl('Y'): 
  2387.           case KEY_PGUP:
  2388.       pageup:
  2389.         whereis_pos.row = 0;
  2390.         if(cur_top_line) {
  2391.         scroll_lines = min(max(num_display_lines -
  2392.             ps_global->viewer_overlap, 1), num_display_lines);
  2393.         cur_top_line -= scroll_lines;
  2394.         if(cur_top_line <= 0){
  2395.             cur_top_line = 0;
  2396.             q_status_message1(SM_INFO, 0, 1, "START of %s",
  2397.             STYLE_NAME(style));
  2398.         }
  2399.         }
  2400.         else
  2401.           q_status_message1(SM_ORDER, 0, 1, "Already at start of %s",
  2402.                 STYLE_NAME(style));
  2403.             break;
  2404.  
  2405.  
  2406.             /*---- Scroll down one page -------*/
  2407.           case PF8:
  2408.           case '+':     
  2409.           case ctrl('V'): 
  2410.           case KEY_PGDN:
  2411.           case ' ':
  2412.       pagedown:
  2413.             if(cur_top_line + num_display_lines < get_scroll_text_lines()){
  2414.         whereis_pos.row = 0;
  2415.         scroll_lines = min(max(num_display_lines -
  2416.             ps_global->viewer_overlap, 1), num_display_lines);
  2417.         cur_top_line += scroll_lines;
  2418.  
  2419.         if(cur_top_line + num_display_lines >= get_scroll_text_lines())
  2420.           q_status_message1(SM_INFO, 0, 1, "END of %s",
  2421.             STYLE_NAME(style));
  2422.             }
  2423.         else{
  2424.         if(style == MessageText
  2425.            && F_ON(F_ENABLE_SPACE_AS_TAB, ps_global)){
  2426.             ch = TAB;
  2427.  
  2428.             if(F_ON(F_ENABLE_TAB_DELETES, ps_global)){
  2429.             long save_msgno;
  2430.  
  2431.             /* Let the TAB advance cur msgno for us */
  2432.             save_msgno = mn_get_cur(ps_global->msgmap);
  2433.             cmd_delete(ps_global, ps_global->msgmap, 0);
  2434.             mn_set_cur(ps_global->msgmap, save_msgno);
  2435.             }
  2436.  
  2437.             goto df;
  2438.         }
  2439.         else
  2440.           q_status_message1(SM_ORDER, 0, 1, "Already at end of %s",
  2441.                     STYLE_NAME(style));
  2442.         }
  2443.  
  2444.             break;
  2445.  
  2446.  
  2447.             /*------ Scroll down one line -----*/
  2448.       case KEY_DOWN:
  2449.       case ctrl('N'):
  2450.             if(cur_top_line + num_display_lines < get_scroll_text_lines()){
  2451.         whereis_pos.row = 0;
  2452.             cur_top_line++;
  2453.         if(cur_top_line + num_display_lines >= get_scroll_text_lines())
  2454.           q_status_message1(SM_INFO, 0, 1, "END of %s",
  2455.                   STYLE_NAME(style));
  2456.         }
  2457.         else
  2458.           q_status_message1(SM_ORDER, 0, 1, "Already at end of %s",
  2459.                   STYLE_NAME(style));
  2460.  
  2461.         break;
  2462.  
  2463.  
  2464.             /* ------ Scroll back up one line -------*/
  2465.           case KEY_UP:
  2466.       case ctrl('P'):
  2467.         whereis_pos.row = 0;
  2468.         if(cur_top_line){
  2469.             cur_top_line--;
  2470.         if(cur_top_line == 0)
  2471.           q_status_message1(SM_INFO, 0, 1, "START of %s",
  2472.                     STYLE_NAME(style));
  2473.         }
  2474.         else
  2475.           q_status_message1(SM_ORDER, 0, 1, "Already at start of %s",
  2476.                 STYLE_NAME(style));
  2477.  
  2478.         break;
  2479.         
  2480.  
  2481.             /*---------- Search text (where is) ----------*/
  2482.           case PF12:
  2483.           case 'w':
  2484.           case ctrl('W'):
  2485.         /* PF12 is not whereis in this case */
  2486.         if(style == MessageText && ch == PF12)
  2487.           goto df;
  2488.  
  2489.           whereis:
  2490.             ps_global->mangled_footer = 1;
  2491.         {long start_row;
  2492.          int  start_col;
  2493.  
  2494.          if(F_ON(F_SHOW_CURSOR,ps_global)){
  2495.          if(found_on < 0
  2496.             || found_on >= get_scroll_text_lines()
  2497.             || found_on < cur_top_line
  2498.             || found_on >= cur_top_line + num_display_lines){
  2499.              start_row = cur_top_line;
  2500.              start_col = 0;
  2501.          }
  2502.          else{
  2503.              if(found_on_col < 0){
  2504.              start_row = found_on + 1;
  2505.              start_col = 0;
  2506.              }
  2507.              else{
  2508.              start_row = found_on;
  2509.              start_col = found_on_col+1;
  2510.              }
  2511.          }
  2512.          }
  2513.          else{
  2514.          start_row = (found_on < 0
  2515.                   || found_on >= get_scroll_text_lines()
  2516.                   || found_on < cur_top_line
  2517.                   || found_on >= cur_top_line + num_display_lines)
  2518.                 ? cur_top_line : found_on + 1,
  2519.          start_col = 0;
  2520.          }
  2521.  
  2522.              found_on = search_text(-FOOTER_ROWS(ps_global), start_row,
  2523.                      start_col, tmp_20k_buf, &whereis_pos);
  2524.         }
  2525.  
  2526.             if(found_on >= 0) {
  2527.         result = found_on < cur_top_line;
  2528.         if(F_ON(F_FORCE_LOW_SPEED,ps_global) ||
  2529.            ps_global->low_speed ||
  2530.            F_ON(F_SHOW_CURSOR,ps_global)){
  2531.             if((found_on >= cur_top_line + num_display_lines ||
  2532.                found_on < cur_top_line) &&
  2533.                num_display_lines > ps_global->viewer_overlap){
  2534.             cur_top_line = found_on - ((found_on > 0) ? 1 : 0);
  2535.             if(get_scroll_text_lines()-cur_top_line < 5)
  2536.               cur_top_line = max(0,
  2537.                   get_scroll_text_lines()-min(5,num_display_lines));
  2538.             }
  2539.             /* else leave cur_top_line alone */
  2540.         }
  2541.         else{
  2542.             cur_top_line = found_on - ((found_on > 0) ? 1 : 0);
  2543.             if(get_scroll_text_lines()-cur_top_line < 5)
  2544.               cur_top_line = max(0,
  2545.               get_scroll_text_lines()-min(5,num_display_lines));
  2546.         }
  2547.  
  2548.         whereis_pos.row = whereis_pos.row - cur_top_line + 1;
  2549.         found_on_col = whereis_pos.col;
  2550.         if(tmp_20k_buf[0])
  2551.           q_status_message(SM_ORDER, 0, 3, tmp_20k_buf);
  2552.         else
  2553.           q_status_message2(SM_ORDER, 0, 3,
  2554.                     "%sFound on line %s on screen",
  2555.                     result ? "Search wrapped to start. " : "",
  2556.                     int2string(whereis_pos.row));
  2557.             }
  2558.         else if(found_on == -1)
  2559.           q_status_message(SM_INFO, 0, 2, "Search cancelled");
  2560.             else
  2561.           q_status_message(SM_ORDER | SM_DING, 0, 3, "Word not found");
  2562.  
  2563.             break; 
  2564.  
  2565.  
  2566.             /*-------------- refresh -------------*/
  2567.           case KEY_RESIZE:
  2568.           case ctrl('L'):
  2569.             num_display_lines          = SCROLL_LINES(ps_global);
  2570.         mark_status_dirty();
  2571.         mark_keymenu_dirty();
  2572.         mark_titlebar_dirty();
  2573.             ps_global->mangled_screen = 1;
  2574.         force                     = 1;
  2575.             break;
  2576.  
  2577.  
  2578.             /*------- no op timeout to check for new mail ------*/
  2579.           case NO_OP_IDLE:
  2580.           case NO_OP_COMMAND:
  2581.             break;
  2582.  
  2583.       case ctrl('M'):
  2584.       case ctrl('J'):
  2585.         if(style != MessageText){
  2586.         q_status_message(SM_ORDER | SM_DING, 0, 2,
  2587.                  "No default command in this screen");
  2588.         break;
  2589.         }            /* else fall thru and handle default */
  2590.  
  2591.  
  2592.         /*------- Other commands of error ------*/
  2593.           default:
  2594.       df:
  2595.         whereis_pos.row = 0;
  2596.         if(style == SimpleText){
  2597.         /*
  2598.          * Yet another pitiful hack.
  2599.          */
  2600.         if(ch == 'f' || ch == PF9){     /* Forward */
  2601.             forward_text(ps_global, text, source);
  2602.         }
  2603.         else if((ch == PF10 || ch == 's') && !ps_global->anonymous){
  2604.             char filename[MAXFOLDER+1];
  2605.             int  rv;
  2606.  
  2607.             filename[0] = '\0';
  2608.             ps_global->mangled_footer = 1;
  2609.             while(1) {
  2610.             rv = optionally_enter(filename, -FOOTER_ROWS(ps_global),
  2611.                           0, MAXPATH, 1, 0,
  2612.                           "File to save text in : ",
  2613.                           NULL, NO_HELP, 0);
  2614.             if(rv == 4)
  2615.               redraw_scroll_text();
  2616.             else if(rv != 3)
  2617.               break;
  2618.             }
  2619.  
  2620.             removing_trailing_white_space(filename);
  2621.             removing_leading_white_space(filename);
  2622.             if(rv == 0 && filename[0]){
  2623.             gf_io_t  pc, gc;
  2624.             STORE_S *store=so_get(FileStar,filename,WRITE_ACCESS);
  2625.  
  2626.             if(store){
  2627.                 char *pipe_err;
  2628.  
  2629.                 gf_set_so_writec(&pc, store);
  2630.                 gf_set_readc(&gc, text, (source == CharStar)
  2631.                             ? strlen((char *)text)
  2632.                             : 0L,
  2633.                      source);
  2634.  
  2635.                 gf_filter_init();
  2636.                 if(pipe_err = gf_pipe(gc, pc)){
  2637.                 q_status_message2(SM_ORDER | SM_DING, 3, 3,
  2638.                           "Problem saving to %s: %s",
  2639.                           filename, pipe_err);
  2640.                 }
  2641.                 else
  2642.                   q_status_message1(SM_ORDER,0,2,"Text saved to %s",
  2643.                            filename);
  2644.  
  2645.                 so_give(&store);
  2646.             }
  2647.             else
  2648.               q_status_message2(SM_ORDER | SM_DING,3,3,
  2649.                         "Can't save to %s: %s",
  2650.                         filename,error_description(errno));
  2651.  
  2652.             }
  2653.             else
  2654.               q_status_message(SM_ORDER | SM_DING, 0, 2,
  2655.                        "Save Cancelled");
  2656.         }
  2657.         else if(ch == PF3 || ch == 'q'){
  2658.             done = 1;
  2659.         }
  2660.         else{
  2661.             q_status_message(SM_ORDER | SM_DING, 0, 2,
  2662.                      "Unknown Command");
  2663.         }
  2664.         }
  2665.         else if(style == AttachText && (ch == 's' || ch == PF10) && att) {
  2666.             /*
  2667.              * This section is an expedient hack to get this working.
  2668.              * We'd probably like to pass a "process_cmd()" in as an
  2669.              * argument or something like that.  Also, save_attachment()
  2670.              * opens and reads the attachment again even though we've
  2671.              * already opened and read it before in this case.
  2672.              */
  2673.         save_attachment(-FOOTER_ROWS(ps_global),
  2674.                 mn_m2raw(ps_global->msgmap, 
  2675.                          mn_get_cur(ps_global->msgmap)),
  2676.                 att);
  2677.                 ps_global->mangled_footer = 1;
  2678.  
  2679.         }
  2680.         else if(style == AttachText && (ch == '|' || ch == PF11) && att
  2681.             && F_ON(F_ENABLE_PIPE, ps_global)) {
  2682.             /*
  2683.              * This section is an expedient hack to get this working.
  2684.              * We'd probably like to pass a "process_cmd()" in as an
  2685.              * argument or something like that.  Also, pipe_attachment()
  2686.              * opens and reads the attachment again even though we've
  2687.              * already opened and read it before in this case.
  2688.              */
  2689.         pipe_attachment(mn_m2raw(ps_global->msgmap, 
  2690.                      mn_get_cur(ps_global->msgmap)),
  2691.                 att);
  2692.                 ps_global->mangled_footer = 1;
  2693.  
  2694.         }
  2695.         else if(style == MessageText){
  2696.             result = process_cmd(ps_global, ps_global->msgmap, ch, 0,
  2697.                      orig_ch, &force);
  2698.             dprint(7, (debugfile, "PROCESS_CMD return: %d\n", result));
  2699.  
  2700.                 if(ps_global->next_screen != SCREEN_FUN_NULL || result == 1){
  2701.             done = 1;
  2702.         }
  2703.         else if(!scroll_state(SS_CUR)){
  2704.             num_display_lines          = SCROLL_LINES(ps_global);
  2705.             ps_global->mangled_screen = 1;
  2706.         }
  2707.             }
  2708.         else {
  2709.         if(!ps_global->nr_mode
  2710.            && ((ch == 'm' || ch == PF1)
  2711.                && (style == HelpText || style == MainHelpText))) {
  2712.                     /*---------- Main menu -----------*/
  2713.                     ps_global->next_screen = main_menu_screen;
  2714.                     done = 1;
  2715.         }
  2716.         else if(!ps_global->nr_mode
  2717.             && ((ch == 'b' || ch == PF11)
  2718.                 && (style == HelpText || style == MainHelpText))) {
  2719.                     /*---------- report a bug  -----------*/
  2720.             gripe(ps_global);
  2721.             num_display_lines = SCROLL_LINES(ps_global);
  2722.         }
  2723.         else if((ch == 'z' || ch == PF10) && style == MainHelpText){
  2724.             print_all_help();
  2725.                 }else if(!ps_global->nr_mode && (ch == PF3 || ch == 'e')) {
  2726.                     /*----------- Done -----------*/
  2727.                     done = 1;
  2728.                 }else if((ch == 'y' && !ps_global->anonymous) ||
  2729.          (ch == PF9 && !ps_global->nr_mode) ||
  2730.          (ch == PF11 && ps_global->nr_mode)) {
  2731.                     /*----------- Print ------------*/
  2732.             char message[12];
  2733.             if(style == AttachText){
  2734.             if(att)
  2735.               strcpy(message, "attachment ");
  2736.             else
  2737.               strcpy(message, "text ");
  2738.             }
  2739.             else if(style == ViewAbookText)
  2740.               strcpy(message, "expanded entry ");
  2741.             else if(style == ViewAbookAtt)
  2742.               strcpy(message, "attachment ");
  2743.             else
  2744.               strcpy(message, "help text ");
  2745.  
  2746.             print_to_printer(text, source, message);
  2747.                 }
  2748.         else {        /*----------- Unknown command -------*/
  2749. unknown:
  2750.             bogus_command(orig_ch,
  2751.                   (style == MainHelpText || style == HelpText)
  2752.                     ? NULL
  2753.                     : F_ON(F_USE_FK,ps_global) ? "F1" : "?");
  2754.                 }
  2755.             }
  2756.             break;
  2757.  
  2758.         } /* End of switch() */
  2759.  
  2760.     } /* End of while() -- loop executing commands */
  2761.  
  2762.     ps_global->redrawer    = NULL;    /* next statement makes this invalid! */
  2763.     zero_scroll_text();        /* very important to zero out on return!!! */
  2764.     scroll_state(SS_FREE);
  2765. #ifdef    _WINDOWS
  2766.     scroll_setrange(0);
  2767. #endif
  2768. }
  2769.  
  2770.  
  2771.  
  2772. /*----------------------------------------------------------------------
  2773.       Print text on paper
  2774.  
  2775.     Args:  text -- The text to print out
  2776.        source -- What type of source text is
  2777.        message -- Message for open_printer()
  2778.     Handling of error conditions is very poor.
  2779.  
  2780.   ----*/
  2781. static int
  2782. print_to_printer(text, source, message)
  2783.      void        *text;        /* the data to be printed */
  2784.      SourceType         source;    /* char **, char * or FILE * */
  2785.      char        *message;
  2786. {
  2787.     register char **t;
  2788.  
  2789.     if(open_printer(message) != 0)
  2790.       return(-1);
  2791.  
  2792.     if(source == CharStar && text != (char *)NULL) {
  2793.         print_text((char *)text);
  2794.  
  2795.     } else if(source == CharStarStar && text != (char **)NULL) {
  2796.         for(t = text; *t != NULL; t++) {
  2797.             print_text(*t);
  2798.         print_text(NEWLINE);
  2799.         }
  2800.  
  2801.     } else if(source == FileStar && text != (FILE *)NULL) {
  2802.     size_t n;
  2803.     int i;
  2804.     fseek((FILE *)text, 0L, 0);
  2805.     n = 20480 - 1;
  2806.     while(i=fread((void *)tmp_20k_buf, sizeof(char), n, (FILE *)text)) {
  2807.         tmp_20k_buf[i] = '\0';
  2808.         print_text(tmp_20k_buf);
  2809.     }
  2810.     }
  2811.  
  2812.     close_printer();
  2813.     return(0);
  2814. }
  2815.  
  2816.  
  2817. /*----------------------------------------------------------------------
  2818.    Search text being viewed (help or message)
  2819.  
  2820.       Args: q_line      -- The screen line to prompt for search string on
  2821.             start_line  -- Line number in text to begin search on
  2822.             start_col   -- Column to begin search at in first line of text
  2823.             cursor_pos  -- position of cursor is returned to caller here
  2824.                (Actually, this isn't really the position of the
  2825.                 cursor because we don't know where we are on the
  2826.                 screen.  So row is set to the line number and col
  2827.                 is set to the right column.)
  2828.  
  2829.     Result: returns line number string was found on
  2830.             -1 for cancel
  2831.             -2 if not found
  2832.  ---*/
  2833. int
  2834. search_text(q_line, start_line, start_col, report, cursor_pos)
  2835.     int   q_line;
  2836.     long  start_line;
  2837.     int   start_col;
  2838.     char *report;
  2839.     Pos  *cursor_pos;
  2840. {
  2841.     char        prompt[MAX_SEARCH+50], nsearch_string[MAX_SEARCH+1];
  2842.     HelpType    help;
  2843.     int         rc;
  2844.     static char search_string[MAX_SEARCH+1] = { '\0' };
  2845.     static ESCKEY_S word_search_key[] = { { 0, 0, "", "" },
  2846.                      {ctrl('Y'), 10, "^Y", "First Line"},
  2847.                      {ctrl('V'), 11, "^V", "Last Line"},
  2848.                      {-1, 0, NULL, NULL}
  2849.                     };
  2850.  
  2851.     report[0] = '\0';
  2852.     sprintf(prompt, "Word to search for [%s] : ", search_string);
  2853.     help = NO_HELP;
  2854.     nsearch_string[0] = '\0';
  2855.  
  2856.     while(1) {
  2857.         rc = optionally_enter(nsearch_string, q_line, 0, MAX_SEARCH, 1, 0,
  2858.                               prompt, word_search_key, help, 0);
  2859.         if(rc == 3) {
  2860.             help = help == NO_HELP ? h_oe_searchview : NO_HELP;
  2861.             continue;
  2862.         }
  2863.     else if(rc == 10){
  2864.         strcpy(report, "Searched to First Line.");
  2865.         cursor_pos->row = 0;
  2866.         cursor_pos->col = 0;
  2867.         return(0);
  2868.     }
  2869.     else if(rc == 11){
  2870.         strcpy(report, "Searched to Last Line."); 
  2871.         cursor_pos->row = max(get_scroll_text_lines() - 1, 0);
  2872.         cursor_pos->col = 0;
  2873.         return(cursor_pos->row);
  2874.     }
  2875.  
  2876.         if(rc != 4)
  2877.           break;
  2878.     }
  2879.  
  2880.     if(rc == 1 || (search_string[0] == '\0' && nsearch_string[0] == '\0'))
  2881.       return(-1);
  2882.  
  2883.     if(nsearch_string[0] != '\0')
  2884.       strcpy(search_string, nsearch_string);
  2885.  
  2886.     rc = search_scroll_text(start_line, start_col, search_string, cursor_pos);
  2887.     return(rc);
  2888. }
  2889.  
  2890.  
  2891.  
  2892. /*----------------------------------------------------------------------
  2893.   Update the scroll tool's titlebar
  2894.  
  2895.     Args:  title -- title to put on titlebar
  2896.        style -- type of titlebar to draw
  2897.        redraw -- flag to force updating
  2898.  
  2899.   ----*/
  2900. void
  2901. update_scroll_titlebar(title, style, cur_top_line, redraw)
  2902.     char     *title;
  2903.     TextType  style;
  2904.     long      cur_top_line;
  2905.     int          redraw;
  2906. {
  2907.     SCROLL_S *st = scroll_state(SS_CUR);
  2908.     int  num_display_lines = SCROLL_LINES(ps_global);
  2909.     long new_line = (cur_top_line + num_display_lines > st->num_lines)
  2910.              ? st->num_lines
  2911.              : cur_top_line + num_display_lines;
  2912.  
  2913.     if(redraw){
  2914.     set_titlebar(title, ps_global->mail_stream,
  2915.              ps_global->context_current, ps_global->cur_folder,
  2916.              ps_global->msgmap, 1,
  2917.              (style == HelpText || style == MainHelpText
  2918.               || style == ComposerHelpText || style == ViewAbookText
  2919.               || style == ViewAbookAtt)
  2920.                ? TextPercent
  2921.                : (style == SimpleText) 
  2922.                ? FileTextPercent 
  2923.                : MsgTextPercent,
  2924.              new_line, st->num_lines);
  2925.  
  2926.     ps_global->mangled_header = 0;
  2927.     }
  2928.     else if(style == SimpleText || style == HelpText
  2929.         || style == MainHelpText || style == ComposerHelpText
  2930.         || style == ViewAbookText || style == ViewAbookAtt)
  2931.       update_titlebar_lpercent(new_line);
  2932.     else
  2933.       update_titlebar_percent(new_line);
  2934. }
  2935.  
  2936.  
  2937.  
  2938. /*----------------------------------------------------------------------
  2939.   manager of global (to this module, anyway) scroll state structures
  2940.  
  2941.  
  2942.   ----*/
  2943. SCROLL_S *
  2944. scroll_state(func)
  2945.     int func;
  2946. {
  2947.     struct scrollstack {
  2948.     SCROLL_S s;
  2949.     struct scrollstack *prev;
  2950.     } *s;
  2951.     static struct scrollstack *stack = NULL;
  2952.  
  2953.     switch(func){
  2954.       case SS_CUR:            /* no op */
  2955.     break;
  2956.       case SS_NEW:
  2957.     s = (struct scrollstack *)fs_get(sizeof(struct scrollstack));
  2958.     memset((void *)s, 0, sizeof(struct scrollstack));
  2959.     s->prev = stack;
  2960.     stack  = s;
  2961.     break;
  2962.       case SS_FREE:
  2963.     if(stack){
  2964.         s = stack->prev;
  2965.         fs_give((void **)&stack);
  2966.         stack = s;
  2967.     }
  2968.     break;
  2969.       default:                /* BUG: should complain */
  2970.     break;
  2971.     }
  2972.  
  2973.     return(stack ? &stack->s : NULL);
  2974. }
  2975.  
  2976.  
  2977.  
  2978. /*----------------------------------------------------------------------
  2979.       Save all the data for scrolling text and paint the screen
  2980.  
  2981.  
  2982.   ----*/
  2983. void
  2984. set_scroll_text(st, text, current_line, source, style)
  2985.     SCROLL_S    *st;
  2986.     void    *text;
  2987.     long     current_line;
  2988.     SourceType     source;
  2989.     TextType     style;
  2990. {
  2991.     /* save all the stuff for possible asynchronous redraws */
  2992.     st->text               = text;
  2993.     st->top_text_line      = current_line;
  2994.     st->screen_start_line  = SCROLL_LINES_ABOVE(ps_global);
  2995.     st->screen_other_lines = SCROLL_LINES_ABOVE(ps_global)
  2996.                 + SCROLL_LINES_BELOW(ps_global);
  2997.     st->source             = source;
  2998.     st->style           = style;
  2999.     st->screen_width       = -1;    /* Force text formatting calculation */
  3000. }
  3001.  
  3002.  
  3003.  
  3004. /*----------------------------------------------------------------------
  3005.      Redraw the text on the screen, possibly reformatting if necessary
  3006.  
  3007.    Args None
  3008.  
  3009.  ----*/
  3010. void
  3011. redraw_scroll_text()
  3012. {
  3013.     int          i, len, offset;
  3014.     SCROLL_S *st = scroll_state(SS_CUR);
  3015.  
  3016.     format_scroll_text();
  3017.  
  3018.     offset = (st->source == FileStar) ? 0 : st->top_text_line;
  3019.  
  3020. #ifdef _WINDOWS
  3021.     mswin_beginupdate();
  3022. #endif
  3023.     /*---- Actually display the text on the screen ------*/
  3024.     for(i = 0; i < PGSIZE(st); i++){
  3025.     ClearLine(i + st->screen_start_line);
  3026.         if((offset + i) < st->num_lines) {
  3027.             len = min(st->line_lengths[offset + i], st->screen_width);
  3028.             PutLine0n8b(i + st->screen_start_line, 0,
  3029.                       st->text_lines[offset + i], len);
  3030.         }
  3031.     }
  3032.  
  3033. #ifdef _WINDOWS
  3034.     mswin_endupdate();
  3035. #endif
  3036.     fflush(stdout);
  3037. }
  3038.  
  3039.  
  3040.  
  3041.  
  3042. /*----------------------------------------------------------------------
  3043.   Free memory used as scrolling buffers for text on disk.  Also mark
  3044.   text_lines as available
  3045.   ----*/
  3046. void
  3047. zero_scroll_text()
  3048. {
  3049.     SCROLL_S     *st = scroll_state(SS_CUR);
  3050.     register int  i;
  3051.  
  3052.     for(i = 0; i < st->lines_allocated; i++)
  3053.       if(st->source == FileStar && st->text_lines[i])
  3054.     fs_give((void **)&st->text_lines[i]);
  3055.       else
  3056.     st->text_lines[i] = NULL;
  3057.  
  3058.     if(st->source == FileStar && st->findex != NULL){
  3059.     fclose(st->findex);
  3060.     st->findex = NULL;
  3061.     if(st->fname){
  3062.         unlink(st->fname);
  3063.         fs_give((void **)&st->fname);
  3064.     }
  3065.     }
  3066.  
  3067.     if(st->text_lines)
  3068.       fs_give((void **)&st->text_lines);
  3069.  
  3070.     if(st->line_lengths)
  3071.       fs_give((void **)&st->line_lengths);
  3072. }
  3073.  
  3074.  
  3075.  
  3076. /*----------------------------------------------------------------------
  3077.  
  3078. Always format at least 20 chars wide. Wrapping lines would be crazy for
  3079. screen widths of 1-20 characters 
  3080.   ----*/
  3081. void
  3082. format_scroll_text()
  3083. {
  3084.     int             i, line_len;
  3085.     char           *p, **pp;
  3086. #ifdef    X_NEW
  3087.     char           *max_line;
  3088. #endif
  3089.     SCROLL_S        *st = scroll_state(SS_CUR);
  3090.     register short  *ll;
  3091.     register char  **tl, **tl_end, *last_space;
  3092.  
  3093.     if(!st || (st->screen_width == (i = ps_global->ttyo->screen_cols)
  3094.            && st->screen_length == PGSIZE(st)))
  3095.         return;
  3096.  
  3097.     st->screen_width = max(20, i);
  3098.     st->screen_length = PGSIZE(st);
  3099.  
  3100.     if(st->lines_allocated == 0) {
  3101.         st->lines_allocated = TYPICAL_BIG_MESSAGE_LINES;
  3102.         st->text_lines = (char **)fs_get(st->lines_allocated *sizeof(char *));
  3103.     memset(st->text_lines, 0, st->lines_allocated * sizeof(char *));
  3104.         st->line_lengths = (short *)fs_get(st->lines_allocated *sizeof(short));
  3105.     }
  3106.  
  3107.     tl     = st->text_lines;
  3108.     ll     = st->line_lengths;
  3109.     tl_end = &st->text_lines[st->lines_allocated];
  3110.  
  3111.     if(st->source == CharStarStar) {
  3112.         /*---- original text is already list of lines -----*/
  3113.         /*   The text could be wrapped nicely for narrow screens; for now
  3114.              it will get truncated as it is displayed */
  3115.         for(pp = (char **)st->text; *pp != NULL;) {
  3116.             *tl++ = *pp++;
  3117.             *ll++ = st->screen_width;
  3118.             if(tl >= tl_end) {
  3119.         i = tl - st->text_lines;
  3120.                 st->lines_allocated *= 2;
  3121.                 fs_resize((void **)&st->text_lines,
  3122.                           st->lines_allocated * sizeof(char *));
  3123.                 fs_resize((void **)&st->line_lengths,
  3124.                           st->lines_allocated*sizeof(short));
  3125.                 tl     = &st->text_lines[i];
  3126.                 ll     = &st->line_lengths[i];
  3127.                 tl_end = &st->text_lines[st->lines_allocated];
  3128.             }
  3129.         }
  3130.  
  3131.     st->num_lines = tl - st->text_lines;
  3132.     } else if (st->source == CharStar) {
  3133.         /*------ Format the plain text ------*/
  3134.         for(p = (char *)st->text; *p; ) {
  3135.             *tl = p;
  3136.             line_len = 0;            
  3137.             last_space = NULL;
  3138.  
  3139.             while(*p && !(*p == RETURN || *p == LINE_FEED)){
  3140.         if(*p == ESCAPE && (i = match_escapes(p+1))) {
  3141.             while(i--)            /* Don't count escape in len */
  3142.               p++;
  3143.                 } else if(*p == TAB) {
  3144.             while(line_len < st->screen_width
  3145.               && ((++line_len)&0x07) != 0) /* add tab's spaces */
  3146.               ;
  3147.  
  3148.                     p++;
  3149.                     last_space = p;
  3150.                 } else if(*p == TAG_EMBED){    /* this char and next are */
  3151.                     p += 2;            /* embedded data (not shown) */
  3152.                 } else if(*p == ' ') {
  3153.                     last_space = ++p;
  3154.                     line_len++;
  3155.                 } else {
  3156.                     p++;
  3157.                     line_len++;
  3158.                 }
  3159.  
  3160.                 if(line_len >= st->screen_width){
  3161.             if(last_space)
  3162.               p = last_space;
  3163.  
  3164.             break;
  3165.                 }
  3166.             }
  3167.         
  3168.             *ll = p - *tl;
  3169.             ll++; tl++;
  3170.             if(tl >= tl_end) {
  3171.         i = tl - st->text_lines;
  3172.                 st->lines_allocated *= 2;
  3173.                 fs_resize((void **)&st->text_lines,
  3174.                           st->lines_allocated * sizeof(char *));
  3175.                 fs_resize((void **)&st->line_lengths,
  3176.                          st->lines_allocated*sizeof(short));
  3177.                 tl     = &st->text_lines[i];
  3178.                 ll     = &st->line_lengths[i];
  3179.                 tl_end = &st->text_lines[st->lines_allocated];
  3180.             }      
  3181.             if(*p == '\r' && *(p+1) == '\n') 
  3182.               p += 2;
  3183.             else if(*p == '\n' || *p == '\r')
  3184.               p++;
  3185.         }
  3186.  
  3187.     st->num_lines = tl - st->text_lines;
  3188.     }
  3189.     else {
  3190.     /*------ Display text is in a file --------*/
  3191.  
  3192.     /*
  3193.      * This is pretty much only useful under DOS where we can't fit
  3194.      * all of big messages in core at once.  This scheme makes
  3195.      * some simplifying assumptions:
  3196.      *  1. Lines are on disk just the way we'll display them.  That
  3197.      *     is, line breaks and such are left to the function that
  3198.      *     writes the disk file to catch and fix.
  3199.      *  2. We get away with this mainly because the DOS display isn't
  3200.      *     going to be resized out from under us.
  3201.      *
  3202.      * The idea is to use the already alloc'd array of char * as a 
  3203.      * buffer for sections of what's on disk.  We'll set up the first
  3204.      * few lines here, and read new ones in as needed in 
  3205.      * scroll_scroll_text().
  3206.      *  
  3207.      * but first, make sure there are enough buffer lines allocated
  3208.      * to serve as a place to hold lines from the file.
  3209.      *
  3210.      *   Actually, this is also used under windows so the display will
  3211.      *   be resized out from under us.  So I changed the following
  3212.      *   to always
  3213.      *    1.  free old text_lines, which may have been allocated
  3214.      *        for a narrow screen.
  3215.      *    2.  insure we have enough text_lines
  3216.      *    3.  reallocate all text_lines that are needed.
  3217.      *   (tom unger  10/26/94)
  3218.      */
  3219.  
  3220.     /* free old text lines, which may be too short. */
  3221.     for(i = 0; i < st->lines_allocated; i++)
  3222.       if(st->text_lines[i])         /* clear alloc'd lines */
  3223.         fs_give((void **)&st->text_lines[i]);
  3224.  
  3225.         /* Insure we have enough text lines. */
  3226.     if(st->lines_allocated < (2 * PGSIZE(st)) + 1){
  3227.         st->lines_allocated = (2 * PGSIZE(st)) + 1; /* resize */
  3228.  
  3229.         fs_resize((void **)&st->text_lines,
  3230.               st->lines_allocated * sizeof(char *));
  3231.         memset(st->text_lines, 0, st->lines_allocated * sizeof(char *));
  3232.         fs_resize((void **)&st->line_lengths,
  3233.               st->lines_allocated*sizeof(short));
  3234.     }
  3235.  
  3236.     /* reallocate all text lines that are needed. */
  3237.     for(i = 0; i <= PGSIZE(st); i++)
  3238.       if(st->text_lines[i] == NULL)
  3239.         st->text_lines[i] = (char *)fs_get(st->screen_width*sizeof(char));
  3240.  
  3241.     tl = &st->text_lines[i];
  3242.  
  3243.     st->num_lines = make_file_index();
  3244.  
  3245.     ScrollFile(st->top_text_line);        /* then load them up */
  3246.  
  3247.     }
  3248.  
  3249. #ifdef    _WINDOWS
  3250.     scroll_setrange (st->num_lines);
  3251. #endif
  3252.  
  3253.     *tl = NULL;
  3254. }
  3255.  
  3256.  
  3257.  
  3258.  
  3259. /*
  3260.  * ScrollFile - scroll text into the st struct file making sure 'line'
  3261.  *              of the file is the one first in the text_lines buffer.
  3262.  *
  3263.  *   NOTE: talk about massive potential for tuning...
  3264.  *         Goes without saying this is still under constuction
  3265.  */
  3266. void
  3267. ScrollFile(line)
  3268.     long line;
  3269. {
  3270.     SCROLL_S     *st = scroll_state(SS_CUR);
  3271.     register int  i;
  3272.              long x;
  3273.  
  3274.     if(line <= 0){        /* reset and load first couple of pages */
  3275.     fseek((FILE *) st->text, 0L, 0);
  3276.     }
  3277.     else{
  3278.     /*** do stuff to get the file pointer into the right place ***/
  3279.     /*
  3280.      * BOGUS: this is painfully crude right now, but I just want to get
  3281.      * it going. 
  3282.      *
  3283.      * possibly in the near furture, an array of indexes into the 
  3284.      * file that are the offset for the beginning of each line will
  3285.      * speed things up.  Of course, this
  3286.      * will have limits, so maybe a disk file that is an array
  3287.      * of indexes is the answer.
  3288.      */
  3289.     fseek(st->findex, (size_t)(line) * sizeof(long), 0);
  3290.     if(fread(&x, sizeof(long), (size_t)1, st->findex) != 1){
  3291.         return;
  3292.     }
  3293.  
  3294.     fseek((FILE *) st->text, x, 0);
  3295.     }
  3296.  
  3297.     for(i = 0; i < PGSIZE(st); i++){
  3298.     if(!st->text_lines || !st->text_lines[i]
  3299.        || fgets(st->text_lines[i],st->screen_width,
  3300.             (FILE *)st->text) == NULL)
  3301.       break;
  3302.  
  3303.     st->line_lengths[i] = strlen(st->text_lines[i]);
  3304.     }
  3305.  
  3306.     for(; i < PGSIZE(st); i++)
  3307.       if(st->text_lines && st->text_lines[i]){/* blank out any unused lines */
  3308.       *st->text_lines[i]  = '\0';
  3309.       st->line_lengths[i] = 0;
  3310.       }
  3311. }
  3312.  
  3313.  
  3314. /*
  3315.  * make_file_index - do a single pass over the file containing the text
  3316.  *                   to display, recording line lengths and offsets.
  3317.  *    NOTE: This is never really to be used on a real OS with virtual
  3318.  *          memory.  This is the whole reason st->findex exists.  Don't
  3319.  *          want to waste precious memory on a stupid array that could 
  3320.  *          be very large.
  3321.  */
  3322. long
  3323. make_file_index()
  3324. {
  3325.     SCROLL_S      *st = scroll_state(SS_CUR);
  3326.     register long  l = 0;
  3327.     long       i = 0;
  3328.  
  3329.     if(!st->findex){
  3330.     if(!st->fname)
  3331.       st->fname = temp_nam(NULL, "pi");
  3332.  
  3333.     if((st->findex = fopen(st->fname,"w+b")) == NULL)
  3334.       return(0);
  3335.     }
  3336.     else
  3337.       fseek(st->findex, 0L, 0);
  3338.  
  3339.     fseek((FILE *)st->text, 0L, 0);
  3340.  
  3341.     fwrite((void *)&i, sizeof(long), (size_t)1, st->findex);
  3342.     while(fgets(tmp_20k_buf, st->screen_width, (FILE *)st->text) != NULL){
  3343.     i = ftell((FILE *)st->text);
  3344.     fwrite((void *)&i, sizeof(long), (size_t)1, st->findex);
  3345.     l++;
  3346.     }
  3347.  
  3348.     fseek((FILE *)st->text, 0L, 0);
  3349.  
  3350.     return(l);
  3351. }
  3352.  
  3353.  
  3354.  
  3355. /*----------------------------------------------------------------------
  3356.      Scroll the text on the screen
  3357.  
  3358.    Args:  new_top_line -- The line to be displayed on top of the screen
  3359.           redraw -- Flag to force a redraw even in nothing changed 
  3360.  
  3361.    Returns: resulting top line
  3362.    Note: the returned line number may be less than new_top_line if
  3363.      reformatting caused the total line count to change.
  3364.  
  3365.  ----*/
  3366. long
  3367. scroll_scroll_text(new_top_line, redraw)
  3368.     long new_top_line;
  3369.     int     redraw;
  3370. {
  3371.     SCROLL_S *st = scroll_state(SS_CUR);
  3372.     int          num_display_lines, len, l;
  3373.  
  3374.     num_display_lines = PGSIZE(st);
  3375.  
  3376.     if(st->top_text_line == new_top_line && !redraw)
  3377.       return(new_top_line);
  3378.  
  3379.     format_scroll_text();
  3380.  
  3381.     if(st->top_text_line >= st->num_lines)    /* don't pop line count */
  3382.       new_top_line = st->top_text_line = max(st->num_lines - 1, 0);
  3383.  
  3384.     if(st->source == FileStar)
  3385.       ScrollFile(new_top_line);        /* set up new st->text_lines */
  3386.  
  3387. #ifdef    _WINDOWS
  3388.     scroll_setrange (st->num_lines);
  3389.     scroll_setpos (new_top_line);
  3390. #endif
  3391.  
  3392.     /* --- 
  3393.        Check out the scrolling situation. If we want to scroll, but BeginScroll
  3394.        says we can't then repaint,  + 10 is so we repaint most of the time.
  3395.       ----*/
  3396.     if(redraw ||
  3397.        (st->top_text_line - new_top_line + 10 >= num_display_lines ||
  3398.         new_top_line - st->top_text_line + 10 >= num_display_lines) ||
  3399.     BeginScroll(st->screen_start_line,
  3400.                     st->screen_start_line + num_display_lines - 1) != 0) {
  3401.         /* Too much text to scroll, or can't scroll -- just repaint */
  3402.         st->top_text_line = new_top_line;
  3403.         redraw_scroll_text();
  3404.     }
  3405.     else{
  3406.     if(new_top_line > st->top_text_line){
  3407.         /*------ scroll down ------*/
  3408.         while(new_top_line > st->top_text_line) {
  3409.         ScrollRegion(1);
  3410.         if(st->source == FileStar)
  3411.           l = num_display_lines - (new_top_line - st->top_text_line);
  3412.         else
  3413.           l = st->top_text_line + num_display_lines;
  3414.         if(l < st->num_lines) {
  3415.             len = min(st->line_lengths[l], st->screen_width);
  3416.             PutLine0n8b(st->screen_start_line + num_display_lines - 1,
  3417.                 0, st->text_lines[l], len);
  3418.         }
  3419.         st->top_text_line++;
  3420.         }
  3421.     }
  3422.     else{
  3423.         /*------ scroll up -----*/
  3424.         while(new_top_line < st->top_text_line) {
  3425.         ScrollRegion(-1);
  3426.         st->top_text_line--;
  3427.         if(st->source == FileStar)
  3428.           l = st->top_text_line - new_top_line;
  3429.         else
  3430.           l = st->top_text_line;
  3431.         len = min(st->line_lengths[l], st->screen_width);
  3432.         PutLine0n8b(st->screen_start_line, 0, st->text_lines[l], len);
  3433.         }
  3434.     }
  3435.  
  3436.     EndScroll();
  3437.     fflush(stdout);
  3438.     }
  3439.  
  3440.     return(new_top_line);
  3441. }
  3442.  
  3443.  
  3444.  
  3445. /*----------------------------------------------------------------------
  3446.       Search the set scrolling text
  3447.  
  3448.    Args:   start_line -- line to start searching on
  3449.        start_col  -- column to start searching at in first line
  3450.            word       -- string to search for
  3451.            cursor_pos -- position of cursor is returned to caller here
  3452.              (Actually, this isn't really the position of the
  3453.               cursor because we don't know where we are on the
  3454.               screen.  So row is set to the line number and col
  3455.               is set to the right column.)
  3456.  
  3457.    Returns: the line the word was found on or -2 if it wasn't found
  3458.  
  3459.  ----*/
  3460. int
  3461. search_scroll_text(start_line, start_col, word, cursor_pos)
  3462.     long  start_line;
  3463.     int   start_col;
  3464.     char *word;
  3465.     Pos  *cursor_pos;
  3466. {
  3467.     SCROLL_S *st = scroll_state(SS_CUR);
  3468.     char      tmp[MAX_SCREEN_COLS+1], *wh, *p;
  3469.     long      l, offset, dlines;
  3470.  
  3471.     dlines = PGSIZE(st);
  3472.     offset = (st->source == FileStar) ? st->top_text_line : 0;
  3473.  
  3474.     if(start_line < st->num_lines){
  3475.     /* search first line starting at position start_col in */
  3476.     strncpy(tmp, st->text_lines[start_line - offset],
  3477.         min(st->line_lengths[start_line - offset], MAX_SCREEN_COLS));
  3478.     tmp[min(st->line_lengths[start_line - offset],
  3479.         MAX_SCREEN_COLS) + 1] = '\0';
  3480.     p = tmp + start_col;
  3481.     if((wh=srchstr(p, word)) != NULL){
  3482.         cursor_pos->row = start_line;
  3483.         cursor_pos->col = wh - tmp;
  3484.         return(start_line);
  3485.     }
  3486.  
  3487.     if(st->source == FileStar)
  3488.       offset++;
  3489.  
  3490.     for(l = start_line+1; l < st->num_lines; l++) {
  3491.         if(st->source == FileStar && l > offset + dlines)
  3492.           ScrollFile(offset += dlines);
  3493.  
  3494.         strncpy(tmp, st->text_lines[l-offset], 
  3495.             min(st->line_lengths[l-offset], MAX_SCREEN_COLS));
  3496.         tmp[min(st->line_lengths[l-offset], MAX_SCREEN_COLS) + 1] = '\0';
  3497.         if((wh=srchstr(tmp, word)) != NULL)
  3498.           break;
  3499.     }
  3500.  
  3501.     if(l < st->num_lines) {
  3502.         cursor_pos->row = l;
  3503.         cursor_pos->col = wh - tmp;
  3504.         return(l);
  3505.     }
  3506.     }
  3507.     else
  3508.       start_line = st->num_lines;
  3509.  
  3510.     if(st->source == FileStar)        /* wrap offset */
  3511.       ScrollFile(offset = 0);
  3512.  
  3513.     for(l = 0; l < start_line; l++) {
  3514.     if(st->source == FileStar && l > offset + dlines)
  3515.       ScrollFile(offset += dlines);
  3516.  
  3517.     strncpy(tmp, st->text_lines[l-offset], 
  3518.         min(st->line_lengths[l-offset], MAX_SCREEN_COLS));
  3519.     tmp[min(st->line_lengths[l-offset], MAX_SCREEN_COLS) + 1] = '\0';
  3520.         if((wh=srchstr(tmp, word)) != NULL)
  3521.           break;
  3522.     }
  3523.  
  3524.     if(l == start_line)
  3525.       return(-2);
  3526.     else{
  3527.     cursor_pos->row = l;
  3528.     cursor_pos->col = wh - tmp;
  3529.         return(l);
  3530.     }
  3531. }
  3532.      
  3533.  
  3534. char *    
  3535. display_parameters(parameter_list)
  3536.      PARAMETER *parameter_list;
  3537. {
  3538.     int        longest_attribute;
  3539.     PARAMETER *p;
  3540.     char      *d;
  3541.  
  3542.     if(parameter_list == NULL) {
  3543.         tmp_20k_buf[0] = '\0';
  3544.  
  3545.     } else {
  3546.         longest_attribute = 0;
  3547.     
  3548.         for(p = parameter_list; p != NULL; p = p->next)
  3549.           longest_attribute = max(longest_attribute, (p->attribute == NULL ?
  3550.                                                       0 :
  3551.                                                       strlen(p->attribute)));
  3552.     
  3553.         longest_attribute = min(longest_attribute, 11);
  3554.     
  3555.         d = tmp_20k_buf;
  3556.         for(p = parameter_list; p != NULL; p = p->next) {
  3557.             sprintf(d, "%-*s: %s\n", longest_attribute,
  3558.                     p->attribute != NULL ? p->attribute : "",
  3559.                     p->value     != NULL ? p->value     : "");
  3560.             d += strlen(d);
  3561.         }
  3562.     }
  3563.     return(tmp_20k_buf);
  3564. }
  3565.  
  3566.  
  3567.  
  3568. /*----------------------------------------------------------------------
  3569.     Display the contents of the given file (likely output from some command)
  3570.  
  3571.   Args: filename -- name of file containing output
  3572.     title -- title to be used for screen displaying output
  3573.     alt_msg -- if no output, Q this message instead of the default
  3574.   Returns: none
  3575.  ----*/
  3576. void
  3577. display_output_file(filename, title, alt_msg)
  3578.     char *filename, *title, *alt_msg;
  3579. {
  3580.     int   msg_q = 0, i = 0;
  3581.     char  buf[512], *msg_p[4];
  3582.     FILE *f;
  3583. #define    MAX_SINGLE_MSG_LEN    60
  3584.  
  3585.     if(f = fopen(filename, "r")){
  3586.     buf[0]   = '\0';
  3587.     msg_p[0] = buf;
  3588.     while(fgets(msg_p[msg_q], sizeof(buf) - (msg_p[msg_q] - buf), f) 
  3589.           && msg_q < 3 && (i = strlen(msg_p[msg_q])) < MAX_SINGLE_MSG_LEN){
  3590.         msg_p[msg_q+1] = msg_p[msg_q]+strlen(msg_p[msg_q]);
  3591.         if (*(msg_p[++msg_q] - 1) == '\n')
  3592.           *(msg_p[msg_q] - 1) = '\0';
  3593.     }
  3594.  
  3595.     if(msg_q < 3 && i < MAX_SINGLE_MSG_LEN){
  3596.         if(*msg_p[0])
  3597.           for(i = 0; i < msg_q; i++)
  3598.         q_status_message2(SM_ORDER, 3, 4,
  3599.             "%s Result: %s", title, msg_p[i]);
  3600.         else
  3601.           q_status_message2(SM_ORDER, 0, 4, "%s%s", title,
  3602.         alt_msg ? alt_msg : " command completed");
  3603.     }
  3604.     else{
  3605.         scrolltool(f, title, AttachText, FileStar, NULL);
  3606.         ps_global->mangled_screen = 1;
  3607.     }
  3608.  
  3609.     fclose(f);
  3610.     unlink(filename);
  3611.     }
  3612.     else
  3613.       dprint(2, (debugfile, "Error reopening %s to get results: %s\n",
  3614.          filename, error_description(errno)));
  3615. }
  3616.  
  3617.  
  3618.  
  3619. /*----------------------------------------------------------------------
  3620.       Fetch the requested header fields from the msgno specified
  3621.  
  3622.    Args: stream -- mail stream of open folder
  3623.          msgno -- number of message to get header lines from
  3624.          fields -- array of pointers to desired fields
  3625.  
  3626.    Returns: allocated string containing matched header lines,
  3627.         NULL on error.
  3628.  ----*/
  3629. char *
  3630. xmail_fetchheader_lines(stream, msgno, fields)
  3631.      MAILSTREAM  *stream;
  3632.      long         msgno;
  3633.      char       **fields;
  3634. {
  3635.     int   i, old_prefetch;
  3636.     char *p, *m, *h = NULL, *match = NULL, tmp[MAILTMPLEN];
  3637.     extern int find_field();
  3638.  
  3639.     old_prefetch = (int) mail_parameters(stream, GET_PREFETCH, NULL);
  3640.     (void) mail_parameters(stream, SET_PREFETCH, NULL);
  3641.     h         = mail_fetchheader(stream, msgno);
  3642.     mail_parameters(stream, SET_PREFETCH, (void *) old_prefetch);
  3643.  
  3644.     if(!h)
  3645.       return(NULL);                /* fetchheader return error? */
  3646.  
  3647.     while(find_field(&h, tmp)){
  3648.     for(i = 0; fields[i] && strucmp(tmp, fields[i]); i++)
  3649.       ;
  3650.  
  3651.     if(p = fields[i]){            /* interesting field */
  3652.         /*
  3653.          * Hold off allocating space for matching fields until
  3654.          * we at least find one to copy...
  3655.          */
  3656.         if(!match)
  3657.           match = m = fs_get(strlen(h) + strlen(p) + 1);
  3658.  
  3659.         while(*p)                /* copy field name */
  3660.           *m++ = *p++;
  3661.  
  3662.         while(*h && (*m++ = *h++))        /* header includes colon */
  3663.           if(*(m-1) == '\n' && (*h == '\r' || !isspace((unsigned char)*h)))
  3664.         break;
  3665.  
  3666.         *m = '\0';                /* tie off match string */
  3667.     }
  3668.     else{                    /* no match, pass this field */
  3669.         while(*h && !(*h++ == '\n'
  3670.               && (*h == '\r' || !isspace((unsigned char)*h))))
  3671.           ;
  3672.     }
  3673.     }
  3674.  
  3675.     return(match ? match : cpystr(""));
  3676. }
  3677.  
  3678.  
  3679. /*----------------------------------------------------------------------
  3680.    Fetch everything except the requested header fields from given msgno
  3681.  
  3682.    Args: stream -- mail stream of open folder
  3683.          msgno -- number of message to get header lines from
  3684.          fields -- array of pointers to UNdesired fields
  3685.  
  3686.    Returns: allocated string containing NON-matching header lines,
  3687.         NULL on error.
  3688.  ----*/
  3689. char *
  3690. xmail_fetchheader_lines_not(stream, msgno, fields)
  3691.      MAILSTREAM  *stream;
  3692.      long         msgno;
  3693.      char       **fields;
  3694. {
  3695.     int   i, old_prefetch;
  3696.     char *p, *m, *h = NULL, *match = NULL, tmp[MAILTMPLEN];
  3697.     extern int find_field();
  3698.  
  3699.     old_prefetch = (int) mail_parameters(stream, GET_PREFETCH, NULL);
  3700.     (void) mail_parameters(stream, SET_PREFETCH, NULL);
  3701.     h         = mail_fetchheader(stream, msgno);
  3702.     mail_parameters(stream, SET_PREFETCH, (void *) old_prefetch);
  3703.  
  3704.     if(!h)
  3705.       return(NULL);                /* fetchheader return error? */
  3706.  
  3707.     while(find_field(&h, tmp)){
  3708.     for(i = 0; fields[i] && strucmp(tmp, fields[i]); i++)
  3709.       ;
  3710.  
  3711.     if(!fields[i]){                /* interesting field */
  3712.         /*
  3713.          * Hold off allocating space for matching fields until
  3714.          * we at least find one to copy...
  3715.          */
  3716.         if(!match)
  3717.           match = m = fs_get(strlen(h) + strlen(tmp) + 1);
  3718.  
  3719.         sstrcpy(&m, tmp);            /* copy field name */
  3720.         while(*h && (*m++ = *h++))        /* header includes colon */
  3721.           if(*(m-1) == '\n' && (*h == '\r' || !isspace((unsigned char)*h)))
  3722.         break;
  3723.  
  3724.         *m = '\0';                /* tie off match string */
  3725.     }
  3726.     else{                    /* no match, pass this field */
  3727.         while(*h && !(*h++ == '\n'
  3728.               && (*h == '\r' || !isspace((unsigned char)*h))))
  3729.           ;
  3730.     }
  3731.     }
  3732.  
  3733.     return(match ? match : cpystr(""));
  3734. }
  3735.  
  3736.  
  3737. int
  3738. find_field(h, tmp)
  3739.      char **h;
  3740.      char *tmp;
  3741. {
  3742.     if(!h || !*h || !**h || isspace((unsigned char)**h))
  3743.       return(0);
  3744.  
  3745.     while(**h && **h != ':' && !isspace((unsigned char)**h))
  3746.       *tmp++ = *(*h)++;
  3747.  
  3748.     *tmp = '\0';
  3749.     return(1);
  3750. }
  3751.  
  3752.  
  3753. #ifdef    _WINDOWS
  3754. /*----------------------------------------------------------------------
  3755.     Return characters in scroll tool buffer serially
  3756.  
  3757.    Args: n -- index of char to return
  3758.  
  3759.    Returns: returns the character at index 'n', or -1 on error or
  3760.         end of buffer.
  3761.  
  3762.  ----*/
  3763. int
  3764. mswin_readscrollbuf(n)
  3765.     int n;
  3766. {
  3767.     SCROLL_S *st = scroll_state(SS_CUR);
  3768.     int       c;
  3769.     static char **orig = NULL, **l, *p;
  3770.     static int    lastn;
  3771.  
  3772.     if(!st)
  3773.       return(-1);
  3774.  
  3775.     /*
  3776.      * All of these are mind-numbingly slow at the moment...
  3777.      */
  3778.     switch(st->source){
  3779.       case CharStar :
  3780.     return((n >= strlen((char *)st->text)) ? -1 : ((char *)st->text)[n]);
  3781.  
  3782.       case CharStarStar :
  3783.     /* BUG? is this test rigorous enough? */
  3784.     if(orig != (char **)st->text || n < lastn){
  3785.         lastn = n;
  3786.         if(orig = l = (char **)st->text)    /* reset l and p */
  3787.           p = *l;
  3788.     }
  3789.     else{                /* use cached l and p */
  3790.         c = n;            /* and adjust n */
  3791.         n -= lastn;
  3792.         lastn = c;
  3793.     }
  3794.  
  3795.     while(l){            /* look for 'n' on each line  */
  3796.         for(; n && *p; n--, p++)
  3797.           ;
  3798.  
  3799.         if(n--)            /* 'n' found ? */
  3800.           p = *++l;
  3801.         else
  3802.           break;
  3803.     }
  3804.  
  3805.     return((l && *l) ? *p ? *p : '\n' : -1);
  3806.  
  3807.       case FileStar :
  3808.     return((fseek((FILE *)st->text, (long) n, 0) < 0
  3809.         || (c = fgetc((FILE *)st->text)) == EOF) ? -1 : c);
  3810.  
  3811.       default:
  3812.     return(-1);
  3813.     }
  3814. }
  3815.  
  3816.  
  3817.  
  3818. /*----------------------------------------------------------------------
  3819.      MSWin scroll callback.  Called during scroll message processing.
  3820.          
  3821.  
  3822.  
  3823.   Args: cmd - what type of scroll operation.
  3824.     scroll_pos - paramter for operation.  
  3825.             used as position for SCROLL_TO operation.
  3826.  
  3827.   Returns: TRUE - did the scroll operation.
  3828.        FALSE - was not able to do the scroll operation.
  3829.  ----*/
  3830. int
  3831. scroll_scroll_callback (cmd, scroll_pos)
  3832. int    cmd;
  3833. long    scroll_pos;
  3834. {
  3835.     SCROLL_S   *st = scroll_state(SS_CUR);
  3836.     int        paint = FALSE;
  3837.     int        num_display_lines;
  3838.     int        scroll_lines;
  3839.     int        num_text_lines;
  3840.     char    *message, *msgSTART, *msgEND;
  3841.     long    maxscroll;
  3842.     
  3843.     
  3844.     msgSTART = "START of message text";
  3845.     msgEND = "END of message text";
  3846.     message = NULL;
  3847.     maxscroll = st->num_lines;
  3848.     switch (cmd) {
  3849.     case MSWIN_KEY_SCROLLUPLINE:
  3850.     if(st->top_text_line > 0) {
  3851.         st->top_text_line--;
  3852.         paint = TRUE;
  3853.         if (st->top_text_line <= 0)
  3854.           message = msgSTART;
  3855.         }
  3856.     break;
  3857.  
  3858.     case MSWIN_KEY_SCROLLDOWNLINE:
  3859.         if(st->top_text_line < maxscroll) {
  3860.         st->top_text_line++;
  3861.         paint = TRUE;
  3862.         if (st->top_text_line >= maxscroll) 
  3863.           message = msgEND;
  3864.         }
  3865.     break;
  3866.         
  3867.     case MSWIN_KEY_SCROLLUPPAGE:
  3868.     if(st->top_text_line > 0) {
  3869.         num_display_lines = SCROLL_LINES(ps_global);
  3870.         scroll_lines = min(max(num_display_lines -
  3871.             ps_global->viewer_overlap, 1), num_display_lines);
  3872.         if (st->top_text_line > scroll_lines)
  3873.         st->top_text_line -= scroll_lines;
  3874.         else {
  3875.         st->top_text_line = 0;
  3876.         message = msgSTART;
  3877.         }
  3878.         paint = TRUE;
  3879.         }
  3880.     break;
  3881.         
  3882.     case MSWIN_KEY_SCROLLDOWNPAGE:
  3883.     num_display_lines = SCROLL_LINES(ps_global);
  3884.     if(st->top_text_line  < maxscroll) {
  3885.         scroll_lines = min(max(num_display_lines -
  3886.             ps_global->viewer_overlap, 1), num_display_lines);
  3887.         st->top_text_line += scroll_lines;
  3888.         if (st->top_text_line >= maxscroll) {
  3889.         st->top_text_line = maxscroll;
  3890.         message = msgEND;
  3891.         }
  3892.         paint = TRUE;
  3893.     }
  3894.     break;
  3895.         
  3896.     case MSWIN_KEY_SCROLLTO:
  3897.     if (st->top_text_line != scroll_pos) {
  3898.         st->top_text_line = scroll_pos;
  3899.         if (st->top_text_line == 0)
  3900.         message = msgSTART;
  3901.         else if(st->top_text_line >= maxscroll) 
  3902.         message = msgEND;
  3903.         paint = TRUE;
  3904.         }
  3905.     break;
  3906.     }
  3907.     
  3908.     if (paint) {
  3909.     mswin_beginupdate();
  3910.     update_scroll_titlebar(NULL, st->style, st->top_text_line, 0);
  3911.     (void) scroll_scroll_text(st->top_text_line, 1);
  3912.     if (message != NULL)
  3913.       q_status_message(SM_INFO, 0, 1, message);
  3914.  
  3915.     /* Display is always called so that the "START(END) of message" 
  3916.      * message gets removed when no longer at the start(end). */
  3917.     display_message (KEY_PGDN);
  3918.     mswin_endupdate();
  3919.     }
  3920.  
  3921.     return (TRUE);
  3922. }
  3923. #endif
  3924.